Python解题:Leetcode周赛(303)

发布于:2022-07-26 ⋅ 阅读:(387) ⋅ 点赞:(0)

题目一:第一个出现两次的字母

给你一个由小写英文字母组成的字符串 s ,请你找出并返回第一个出现 两次 的字母。

注意:

  • 如果 a 的 第二次 出现比 b 的 第二次 出现在字符串中的位置更靠前,则认为字母 a 在字母 b 之前出现两次。
  • s 包含至少一个出现两次的字母。

难度:简单

示例 1:

输入:s = "abccbaacz"
输出:"c"
解释:
字母 'a' 在下标 0 、5 和 6 处出现。
字母 'b' 在下标 1 和 4 处出现。
字母 'c' 在下标 2 、3 和 7 处出现。
字母 'z' 在下标 8 处出现。
字母 'c' 是第一个出现两次的字母,因为在所有字母中,'c' 第二次出现的下标是最小的。

示例 2:

输入:s = "abcdd"
输出:"d"
解释:
只有字母 'd' 出现两次,所以返回 'd' 。

分析

这道送分题,可以有N种解法,以至于时间都花在考虑用哪种解法更优了。其实不同解法也只是有细微的差别,无非就是从左至右找出字符串里最先出现的重复的字母。可以比较每个字母在字符串的下标,也可以依次遍历字符串,找到第一个出现两次的字母即返回,还可以对字符串进行切片等等,道理和遍历差不多。这里只写一种就好,因为就本题而言,差别真的不大。

代码实现

s = input()
for i in range(len(s)):
    if s[i] in s[:i]: 
        print(s[i])
        break

题目二:相等行列对

给你一个下标从 0 开始、大小为 n x n 的整数矩阵 grid ,返回满足 Ri 行和 Cj 列相等的行列对 (Ri, Cj) 的数目

如果行和列以相同的顺序包含相同的元素(即相等的数组),则认为二者是相等的。

难度:中等

示例 1:

输入:grid = [[3,2,1],[1,7,6],[2,7,7]]
输出:1
解释:存在一对相等行列对:
- (第 2 行,第 1 列):[2,7,7]

示例 2:

输入:grid = [[3,1,2,2],[1,4,4,5],[2,4,2,2],[2,4,2,2]]
输出:3
解释:存在三对相等行列对:
- (第 0 行,第 0 列):[3,1,2,2]
- (第 2 行, 第 2 列):[2,4,2,2]
- (第 3 行, 第 2 列):[2,4,2,2]

分析

本题要找出有多少组相等的行列对,由于python自己的列表没有矩阵的结构,无法像numpy那样把矩阵转置,得到由列元素组成的二维列表,所以需要先手动把列表进行转置,组成列列表。然后通过遍历原矩阵(行列表),行元素与列元素相同,也就意味着同样的行元素也出现在转置的列表中即可组成符合要求的行列对,于是将每一行元素在转置的列表中出现的次数相加即可。

第二种解法正好反过来,先求出原矩阵中每个行元素出现的次数,然后在转置列表中找到每个列元素在原矩阵出现的次数。其实逻辑是相通的,只不过第二种解法不用先生成转置列表,而是可以使用zip生成一个可迭代对象,在遍历的同时生成转置,从而节省时间。

代码实现

解法一:

grid = eval(input())
trans = []
for i in range(len(grid)):
    trans.append([j[i] for j in grid])
cnt = 0
for i in grid:
    cnt += trans.count(i)
print(cnt)

解法二:

统计原矩阵中相同行元素出现的次数,最好使用字典结构,方便进行查找,而使用Counter对象会更加节省时间。

遍历zip对象的时候,我们只需要将每一个列元素在原矩阵中出现的次数累加即可,所以并不需要生成列表,使用推导式即可完成。

from collections import Counter
grid = eval(input())
row = Counter(tuple(i) for i in grid)
res = sum(row[i] for i in zip(*grid))
print(res)

题目三:设计食物评分系统

设计一个支持下述操作的食物评分系统:

  • 修改 系统中列出的某种食物的评分。
  • 返回系统中某一类烹饪方式下评分最高的食物。

实现 FoodRatings 类:

  • FoodRatings(String[] foods, String[] cuisines, int[] ratings) 初始化系统。食物由 foodscuisines 和 ratings描述,长度均为 n 。
    • foods[i] 是第 i 种食物的名字。
    • cuisines[i] 是第 i 种食物的烹饪方式。
    • ratings[i] 是第 i 种食物的最初评分。
  • void changeRating(String food, int newRating) 修改名字为 food 的食物的评分。
  • String highestRated(String cuisine) 返回指定烹饪方式 cuisine 下评分最高的食物的名字。如果存在并列,返回 字典序较小的名字。

注意,字符串 x 的字典序比字符串 y 更小的前提是:x 在字典中出现的位置在 y 之前,也就是说,要么 x 是 y 的前缀,或者在满足 x[i] != y[i] 的第一个位置 i 处,x[i] 在字母表中出现的位置在 y[i] 之前。

难度:中等

示例:

输入
["FoodRatings", "highestRated", "highestRated", "changeRating", "highestRated", "changeRating", "highestRated"]
[[["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]], ["korean"], ["japanese"], ["sushi", 16], ["japanese"], ["ramen", 16], ["japanese"]]
输出
[null, "kimchi", "ramen", null, "sushi", null, "ramen"]

解释
foodRatings = FoodRatings(["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]);
foodRatings.highestRated("korean") # 返回 "kimchi"
                                   # "kimchi" 是分数最高的韩式料理,评分为 9 。
foodRatings.highestRated("japanese") # 返回 "ramen"
                                     # "ramen" 是分数最高的日式料理,评分为 14 。
foodRatings.changeRating("sushi", 16) # "sushi" 现在评分变更为 16 。
foodRatings.highestRated("japanese") # 返回 "sushi"
                                     # "sushi" 是分数最高的日式料理,评分为 16 。
foodRatings.changeRating("ramen", 16) # "ramen" 现在评分变更为 16 。
foodRatings.highestRated("japanese") # 返回 "ramen"
                                     # "sushi" 和 "ramen" 的评分都是 16 。
                                     # 但是,"ramen" 的字典序比 "sushi" 更小。

分析

构造题。和上一篇双周赛的构造题类似,这题唯一的难度在于如何快速形成有序的数据结构。

因为题目给出了三个字段:cuisine、food、rating,所以为了方便互相引用,至少需要两个字典。字典1将cuisine作为键,用来记录该菜系下有什么food,以及food的评分。而字典2用来记录food所属的菜系以及评分。当然也可以使用三个字典,字典3用来记录同样的评分下面有哪些food及其菜系。但是因为最后的查询函数是找出同一菜系cuisine下评分最高的food,所以字典1必不可少,字典2用来更新评分,而字典3其实并不需要。

和上篇文章类似,可以使用有序列表或堆排序来构建字典1,这样,在放入food和rating的时候,就可以自动排序将符合查询要求的food放在列表首位或堆顶。需要注意的是,我们要维护两个排序,一个是food名称的字典序,另一个是rating排序,所以要把(rating, food)这样的组合里rating在前,food在后,简单说就是最后排序的放在前面,这样有序列表或堆会先按照food再按照rating进行排序。另外要注意的是,有序列表或堆排序默认都是小顶堆,所以food的字典序其实也是ASCII码小的在前面,但是我们是要找出rating最大的,所以在保存的时候,要将rating取反,使最大的变成最小的,这样就自动把raiting最大的放在堆顶了。

字典2的结构随意,只要能够根据food找到其对应的cuisine和rating即可。

代码实现

解法一:使用有序列表 SortedList

class FoodRatings:

    def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):
        from sortedcontainers import SortedList
        self.cuisine = {}
        self.food = {}
        for f, c, r in zip(foods, cuisines, ratings):
            self.food[f] = [c, r]
            self.cuisine.setdefault(c,SortedList()).add((-r, f))

    def changeRating(self, food: str, newRating: int) -> None:
        c, r = self.food[food]
        self.food[food][1] = newRating
        self.cuisine[c].remove((-r, food))
        self.cuisine[c].add((-newRating, food))

    def highestRated(self, cuisine: str) -> str:
        return self.cuisine[cuisine][0][1]

解法二:使用堆排序 heapq

上篇文章讲过,因为heapq没有办法对堆顶之外的数据进行删除和修改,所以需要在查询的时候依次取出堆顶,与字典2进行比较,如果不同,则说明food的rating被修改过,则自动抛弃堆顶,继续取出下一个。如果相同,则还要将取出的数据重新入堆,以方便下次查找。

import heapq

class FoodRatings:

    def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):
        self.cuisine = {}
        self.food = {}
        for f, c, r in zip(foods, cuisines, ratings):
            self.food[f] = [c, r]
            heapq.heappush(self.cuisine.setdefault(c,list()),(-r, f))

    def changeRating(self, food: str, newRating: int) -> None:
        heapq.heappush(self.cuisine[self.food[food][0]],(-newRating, food))
        self.food[food][1] = newRating

    def highestRated(self, cuisine: str) -> str:
        while True:
            r,f = heapq.heappop(self.cuisine[cuisine])
            if self.food[f][1] == -r:
                heapq.heappush(self.cuisine[cuisine],(r, f))
                return f


题目四:优质数对的数目

给你一个下标从 0 开始的正整数数组 nums 和一个正整数 k 。

如果满足下述条件,则数对 (num1, num2) 是 优质数对 :

  • num1 和 num2  在数组 nums 中存在。
  • num1 OR num2 和 num1 AND num2 的二进制表示中值为 1 的位数之和大于等于 k ,其中 OR 是按位  操作,而 AND 是按位 操作。

返回 不同 优质数对的数目。

如果 a != c 或者 b != d ,则认为 (a, b) 和 (c, d) 是不同的两个数对。例如,(1, 2) 和 (2, 1) 不同。

注意:如果 num1 在数组中至少出现 一次 ,则满足 num1 == num2 的数对 (num1, num2) 也可以是优质数对。

难度:困难

示例 1:

输入:nums = [1,2,3,1], k = 3
输出:5
解释:有如下几个优质数对:
- (3, 3):(3 AND 3) 和 (3 OR 3) 的二进制表示都等于 (11) 。值为 1 的位数和等于 2 + 2 = 4 ,大于等于 k = 3 。
- (2, 3) 和 (3, 2): (2 AND 3) 的二进制表示等于 (10) ,(2 OR 3) 的二进制表示等于 (11) 。值为 1 的位数和等于 1 + 2 = 3 。
- (1, 3) 和 (3, 1): (1 AND 3) 的二进制表示等于 (01) ,(1 OR 3) 的二进制表示等于 (11) 。值为 1 的位数和等于 1 + 2 = 3 。
所以优质数对的数目是 5 。

示例 2:

输入:nums = [5,1,1], k = 10
输出:0
解释:该数组中不存在优质数对。

分析

题目说了许多废话,其实“阅读理解”后的归纳就是:从整数数组nums中先后取出两个数作为一组数对,同一个数字可以重复选取,比如 (1,1),而顺序不同的数对视做不同数对,比如 (1,2) 和 (2,1) 就是不同的数对,那么优质数对有多少组?

这题主要的难点在于如何理解“优质数对”的定义,如果对于数对(a,b),两个数字或操作结果的二进制1的个数表示为 f(a|b),与操作表示为 f(a&b),满足“优质数对”条件的函数表示就是

f(a|b) + f(a&b) >= k

通过数学知识,或者通过观察,可以得出:f(a|b) + f(a&b) = f(a) + f(b) 。也就是说,两个数或操作和与操作结果二进制1的位数之和等于二者自身二进制1的位数之和。

这一步很关键,因为它大大优化了我们的查找效率,不然我们需要使用穷举搜索,把从数组中取出两个数的所有可能都找出来,然后依次使用条件 f(a|b) + f(a&b) >= k 来进行判断。算法的基础来自于数据结构。如果没有数据结构,或者是未知的数据结构,就像是在乱序的数字里找到某个特定的数字,只能穷举——虽然穷举也是一种算法。而如果我们知道 f(a|b) + f(a&b) 等价于 f(a) + f(b),那就可以把数组中每个数字对应的二进制1的位数先求出来,根据位数将数字进行排序,形成一个有序的数据结构。只要通过指针遍历,就可以较简单地通过判断 f(a) + f(b) >= k 来将所有结果找出。

因为题目只要求出个数,不需要知道是哪些数对,所以我们可以再进一步,将数字转化成二进制1的位数,形成一个列表,然后再对其进行排序。

第一种解法是两个指针,对该列表从两端开始遍历,外指针从前往后,内指针从后往前。先固定外指针,移动内指针,依次检查列表里两个数字的和是否大于等于k,如果是,则内指针继续向前移,一直到小于 k 或者 pos=0(内指针指向第一个元素,结束遍历)的时候,就表示对于外指针当前指向的数字而言,内指针往后的所有数字都是满足条件的,而这样的长度是列表的长度减去pos。再定义一个累加器,加上这个长度,然后将外指针向后移动一位,检查第二个数字。注意,这时候内指针不需要重置,可以由现在的位置继续前移(如果不为0的话),因为外指针指向的数字比上一个数字大,所以与内指针之后的数字必然满足条件。当外指针指向最后一个数字,累加结束,得到的值就是返回的结果。

第二种解法更加高效,可以根据数组中数字的二进制1的位数作为键,位数相同的数字的个数作为值,组成一个字典,然后同样使用内外两个指针遍历字典,将满足条件的位数所对应的数字出现次数相乘,就是这些数字所能组成“优质数对”的个数,同样累加即可得到结果。而因为使用字典的哈希结构,这一过程将比列表更加省时。

代码实现

解法一:

nums = eval(input())
k = int(input())
temp = sorted([bin(n).count('1') for n in set(nums)])
l = len(temp)
pos = l
res = 0
for i in range(l):
    while pos > 0 and temp[pos-1] + temp[i] >= k:
        pos -= 1
    res += l-pos
print(res)

解法二:

nums = eval(input())
k = int(input())
from collections import Counter
cnt = Counter(bin(n).count('1') for n in set(nums))
res = 0
for a, cnta in cnt.items():
    for b, cntb in cnt.items():
        if a+b >= k: 
            res += cnta*cntb
print(res)