[LeetCode专场复盘] AutoX 安途智行专场竞赛

[LeetCode专场复盘] AutoX 安途智行专场竞赛

    • 一、本周周赛总结
    • 二、 AutoX-1. 网页瀑布流
      • 1. 题目描述
      • 2. 思路分析
      • 3. 代码实现
    • 三、AutoX-2. 蚂蚁王国的蜂蜜
      • 1. 题目描述
      • 2. 思路分析
      • 3. 代码实现
    • 四、AutoX-3. 出行的最少购票费用
      • 1. 题目描述
      • 2. 思路分析
      • 3. 代码实现
    • 五、[Hard] AutoX-4. 蚂蚁爬行
      • 1. 题目描述
      • 2. 思路分析
      • 3. 代码实现
    • 六、参考链接

一、本周周赛总结

  • 这场是后来补的,感觉比较有意义,所以记录一下。
  • T4几何题,希望借这个机会搞一下模板

二、 AutoX-1. 网页瀑布流

链接: AutoX-1. 网页瀑布流

1. 题目描述

[LeetCode专场复盘] AutoX 安途智行专场竞赛_第1张图片

2. 思路分析

没想到第一题就用到了堆,镇住了我。
意识到这不是周赛。

  • 显然,优先级有两个维度,每次使用的列为最短、最靠左的列,使用完还需要加回队列中。
  • 因此需要快速查找当前队列中最短最靠左的列。在length=10**4的情况下,每次查找消耗100次(遍历)其实也能过,但稍显笨拙。
  • 那么优先队列就是比较合适的数据结构。

3. 代码实现

class Solution:
    def getLengthOfWaterfallFlow(self, num: int, block: List[int]) -> int:
        h = [(0,i) for i in range(num)]
        for b in block:
            hi, i = heapq.heappop(h)
            heapq.heappush(h,(hi+b,i))
        return max(a for a,b in h)            

三、AutoX-2. 蚂蚁王国的蜂蜜

链接: AutoX-2. 蚂蚁王国的蜂蜜

1. 题目描述

[LeetCode专场复盘] AutoX 安途智行专场竞赛_第2张图片

2. 思路分析

这题easy就离谱。

  • 一开始是不会做的。
  • 由于方差公式是sum((x-avg)**2)/n,因此计算一次需要遍历每个数字,每次计算n,一共计算n次。
  • 首先看到数据范围n=10**5,显然这样n^2是过不了的。
  • 又仔细看了下,价格是[0,100],且通过样例函数传参发现是整数,那么价格如果可以Counter()压缩一下,计算量就变成n*101,可以过。

更新,这题其实可以O(n),方差公式可以变形:s2 = sum(xi2)-avg2
在这里插入图片描述

[LeetCode专场复盘] AutoX 安途智行专场竞赛_第3张图片

3. 代码实现

class Solution:
    def honeyQuotes(self, handle: List[List[int]]) -> List[float]:
        cnt = Counter()
        s = 0
        c = 0
        ans = []
        for ab in handle:
            a = ab[0]
            if a == 1:
                b = ab[1]
                cnt[b] += 1
                s += b
                c += 1
                
            elif a == 2:
                b = ab[1]
                cnt[b] -= 1
                s -= b
                c -= 1
            elif a == 3:
                if c == 0:
                    ans.append(-1)
                else:
                    ans.append(s/c)
            elif a == 4:
                if c == 0:
                    ans.append(-1)
                else:
                    avg = s / c
                    s2 = 0
                    for k,v in cnt.items():
                        s2 += (k-avg)**2*v
                    ans.append(s2/c)
        return ans
                    
                
class Solution:
    def honeyQuotes(self, handle: List[List[int]]) -> List[float]:
        s2 = 0
        s = 0
        c = 0
        ans = []
        for ab in handle:
            a = ab[0]
            if a == 1:
                b = ab[1]
                s += b
                s2 += b*b
                c += 1
                
            elif a == 2:
                b = ab[1]
                s -= b
                s2 -= b*b
                c -= 1
            elif a == 3:
                if c == 0:
                    ans.append(-1)
                else:
                    ans.append(s/c)
            elif a == 4:
                if c == 0:
                    ans.append(-1)
                else:
                    avg = s / c
                    
                    ans.append(s2/c - avg*avg)
        return ans

四、AutoX-3. 出行的最少购票费用

链接
AutoX-3. 出行的最少购票费用

1. 题目描述

[LeetCode专场复盘] AutoX 安途智行专场竞赛_第4张图片

2. 思路分析

  • dp+二分,注意到数据范围是n=10^5,m=20,大概率是用n*m的算法。因此在每次dp考虑可以遍历tickets。
  • 由于day有序,不需要额外排序。
  • 令f[i]为前i个day有机票覆盖时,最小的花费。
  • 显然第一个day,就是min(tickets),这里前提是数据规定了没有任何一张票覆盖小于1天,即对于一天,任何票都可以飞,取花费最小即可。
  • 对于第i天,如果这天使用的一张票,时长价格为(v,p),那么我可以向前坐,看看这张票能覆盖到多远的之前,这之间的票可以不买了,只买边界最早那张。
  • 计算s = d - v ,即s是用不到这张票的最后一天。
  • 如果s比day[0] (最早一天)还小,说明从开始到这天都可以用这张票搞定f[i] = p。
  • 如果s > day[i-1],即本票不能覆盖前边任意一天,那前边的票依然要买,还要为今天单独买这张票f[i] = p + f[i-1]
  • 否则,s一定在前边买过票的day中间,找到这个位置pos,用这张票覆盖后半部分,只买前半部分的票,f[i] = p + f[pos];这里由于day有序,用二分加速查找这个位置。

3. 代码实现

class Solution:
    def minCostToTravelOnDays(self, days: List[int], tickets: List[List[int]]) -> int:
        n = len(days)
        f = [inf] * n
        f[0] = min(b for a,b in tickets)
        for i in range(1,n):
            d = days[i]
            for v, p in tickets:
                s = d - v 
                if s < days[0]:
                    f[i] = min(f[i], p)
                elif s > days[i-1]:
                    f[i] = min(f[i], p+f[i-1])
                else:
                    pos = bisect_right(days,s) - 1
                    f[i] = min(f[i], p+f[pos])
        return f[-1]
                    

五、[Hard] AutoX-4. 蚂蚁爬行

链接: AutoX-4. 蚂蚁爬行

1. 题目描述

[LeetCode专场复盘] AutoX 安途智行专场竞赛_第5张图片

2. 思路分析

几何题,百度了半天我还是做出来了,很骄傲(叉腰)
  • 题意很简单,给n个图形,有的是线段,有的是圆,问任意两个图形是否相连。
  • 那么用并查集把相交的图形连接起来即可。
  • 问题就转化成了如何判断以下图形是否相交。
    1. 线段与线段
    1. 线段与圆
    1. 圆与圆
    1. 其中圆与圆最简单:
    • 如果两个圆心距离R-r<=d<=R+r,则相交;否则不相交。
    1. 线段与线段也比较简单,抄的:
    • 对于两条线段,如果相交则有以上两种情况:
    • 1)每条线段都分别跨越了另一条线段所在直线,即两个端点分别在该直线两侧;
    • 2)一条线段的端点恰好在另一条线段上。”
      • 这里我们可以用向量叉积来判断跨立条件,如下图所示,如果线段ab跨立了线段cd所在的直线,那么作向量ca、cd和cb,其中ca×cd和cb×cd的正负号应该是不同的。即满足
    • ca×cd和cb×cd < 0
    • 考虑到情况2),上式等于0的情况也成立,因此,判断线段ab跨立线段cd所在直线的的条件为
    • ca×cd和cb×cd <= 0
    • [LeetCode专场复盘] AutoX 安途智行专场竞赛_第6张图片
    1. 圆和线段最难,分情况:(https://blog.csdn.net/songbai1997/article/details/86599879)
  • 用到了距离公式、根据线段求直线、点到直线距离公式、余弦定理。
    • [LeetCode专场复盘] AutoX 安途智行专场竞赛_第7张图片

3. 代码实现

调整后的代码,希望可以在一定程度上当模板用


class UnionFind:
    def __init__(self, eles):
        self.fathers = {}
        f = self.fathers
        for e in eles:
            f[e] = e

    def find_father(self, x):
        f = self.fathers
        f.setdefault(x, x)
        return self._zip_find_father(x)

    def _zip_find_father(self, x):
        fathers = self.fathers
        if fathers[x] != x:
            fathers[x] = self._zip_find_father(fathers[x])
        return fathers[x]

    def union(self, x, y):
        f = self.fathers
        f.setdefault(x, x)
        f.setdefault(y, y)
        x = self.find_father(x)
        y = self.find_father(y)
        if x == y:
            return False
        if x < y:
            x, y = y, x

        self.fathers[x] = y
        return True

    def is_same_father(self, x, y):
        return self.find_father(x) == self.find_father(y)


def point_dis_pow(p1, p2):
    """
    计算两个点的距离的平方(暂不开方防止误差)
    """
    return (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2


def multiply(v1, v2):
    """
    计算两个向量的叉积
    """
    return v1.x * v2.y - v2.x * v1.y


class Point:
    # 点
    __slots__ = 'x', 'y'

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __sub__(self, other):
        """
        重载减法运算,计算两点之差,实际上返回的表示该点到另一点的向量
        :param other: 另一个点
        :return: 该点到另一点的向量
        """
        return Point(self.x - other.x, self.y - other.y)


class Segment:
    # 线段
    __slots__ = 'point1', 'point2'

    def __init__(self, point1, point2):
        self.point1 = point1
        self.point2 = point2

    def straddle(self, another_segment):
        """
        :param another_segment: 另一条线段
        :return: 如果另一条线段跨立该线段,返回True;否则返回False
        """
        v1 = another_segment.point1 - self.point1
        v2 = another_segment.point2 - self.point1
        vm = self.point2 - self.point1
        if multiply(v1, vm) * multiply(v2, vm) <= 0:
            return True
        else:
            return False

    def is_cross(self, another_segment):
        """ 两条线段相交则返回True
        :param another_segment: 另一条线段
        :return: 如果两条线段相互跨立,则相交;否则不相交
        """
        if self.straddle(another_segment) and another_segment.straddle(self):
            return True
        else:
            return False


class Circle:
    # 圆
    __slots__ = 'p', 'r'

    def __init__(self, p, r):
        self.p, self.r = p, r
        # self.x,self.y = self.p.x,self.p.y

    def get_center_point(self):
        """
        获取圆心的点
        """
        return self.p


def is_point_out_circle(c, p):
    """ 判断点相对于圆的位置,通过半径和距离圆心的距离判断
    点在圆外返回0
    点在圆上返回1
    点在圆内返回2
    """
    d = point_dis_pow(p, c.get_center_point())
    r2 = c.r ** 2
    if d > r2:
        return 0
    elif d == r2:
        return 1
    return 2


def judge_circle_seg_cross(c, s):
    """
    判断圆c和线段s是否相交
    :param c:
    :param s:
    :return:
    """
    p1, p2 = s.point1, s.point2
    pos1 = is_point_out_circle(c, s.point1)
    pos2 = is_point_out_circle(c, s.point2)
    if pos1 == 1 or pos2 == 1:  # 0. 任一点在圆上,相交
        return True
    if pos1 == 2 == pos2:  # 1. 两点都在圆内,必不相交
        return False
    if pos1 != pos2:  # 2. 一个点在内一个点在外,相交
        return True

    # 3. 两点都在圆外,先求线段所在的直线方程:Ax+By+C=0
    # print('都在圆外')
    A = p1.y - p2.y
    B = p2.x - p1.x
    C = p1.x * p2.y - p2.x * p1.y
    # 点到直线的距离公式d = |Ax+By+c| / (A**2+B**2)**0.5
    # 3.1 如果 |Ax+By+c| / (A**2+B**2)**0.5 > r**2,不相交
    # 以下下计算为了精度,把不等式化简:左半部分的分母移到右边部分。因此d1只计算分子,把分母乘到d2
    x, y, r2 = c.p.x, c.p.y, c.r ** 2
    # print(x,y,r2,A,B,C)
    d1 = (A * x + B * y + C) ** 2
    d2 = (A * A + B * B) * r2
    # print(d1,d2)
    # 3.1 如果圆心到直线的距离大于半径,不相交
    if d1 > d2:
        return False
    # 3.2 如果距离小于等于半径,则需要用余弦定理计算两个角是否是锐角,都是锐角则相交: 3.3 否则不相交
    # cosA = (b^2+c^2-a^2)//(2*b*c)
    a2 = point_dis_pow(p1, c.get_center_point())  # 边a是角A的对边,因此是线段BC
    b2 = point_dis_pow(p2, c.get_center_point())
    c2 = point_dis_pow(p2, p1)
    # 这里计算如果大于0则是锐角,因此省去分母不做计算
    angleA = (b2 + c2 - a2)
    angleB = (a2 + c2 - b2)
    # print(angleA,angleB)
    if angleA > 0 and angleB > 0:  # 余弦为正是锐角
        return True

    return False


def judge_two_circle_cross(c1, c2):
    r1, r2 = c1.r, c2.r
    return (r1 - r2) ** 2 <= point_dis_pow(c1.get_center_point(), c2.get_center_point()) <= (r1 + r2) ** 2


class Solution:
    def antPass(self, g: List[List[int]], path: List[List[int]]) -> List[bool]:
        n = len(g)
        uf = UnionFind(range(n))
        for i in range(n - 1):
            for j in range(i + 1, n):
                gi, gj = g[i], g[j]  # 简写,减小代码量同时方便交换引用
                ni, nj = len(gi), len(gj)  # 他们的长度 3是圆 ,4是线段

                if ni == nj == 4:  # 如果都是线段,直接调结构
                    a = Segment(Point(gi[0], gi[1]), Point(gi[2], gi[3]))
                    b = Segment(Point(gj[0], gj[1]), Point(gj[2], gj[3]))
                    if a.is_cross(b):
                        # print(i,j)
                        uf.union(i, j)
                elif ni == nj == 3:  # 如果都是圆,用半径和圆心距离比较即可。
                    if judge_two_circle_cross(Circle(Point(gi[0], gi[1]), gi[2]), Circle(Point(gj[0], gj[1]), gj[2]) ):
                        # print(i,j)
                        uf.union(i, j)
                else:  # 如果是一个圆一个线段
                    if ni == 4:  # 为了方便编码,令gi是圆,gj是线段
                        gi, gj = gj, gi
                    if judge_circle_seg_cross(Circle(Point(gi[0], gi[1]), gi[2]),
                                              Segment(Point(gj[0], gj[1]), Point(gj[2], gj[3]))):
                        # print(i,j)
                        uf.union(i, j)

        return [uf.find_father(a) == uf.find_father(b) for a, b in path]

原代码

class UnionFind:
    def __init__(self,eles):
        self.fathers = {}
        f = self.fathers
        for e in eles:
            f[e] = e
        
    def find_father(self,x):
        f = self.fathers
        f.setdefault(x,x)
        return self._zip_find_father(x)    
    def _zip_find_father(self,x):
        fathers = self.fathers
        if fathers[x] != x:
            fathers[x] = self._zip_find_father(fathers[x])
        return fathers[x]                      
    def union(self,x,y):
        f = self.fathers
        f.setdefault(x,x)
        f.setdefault(y,y)
        x = self.find_father(x)
        y = self.find_father(y)
        if x == y:
            return False
        if x<y:
            x,y=y,x

        self.fathers[x] = y       
        return True 
    def is_same_father(self,x,y):
        return self.find_father(x)==self.find_father(y)

def point_dis_pow(p1,p2):
    return (p1.x-p2.x)**2 + (p1.y-p2.y)**2
def multiply(v1, v2):  
    """ 
    计算两个向量的叉积 
    """  
    return v1.x*v2.y - v2.x*v1.y  
class Point:  
    def __init__(self, x, y):  
        self.x = x  
        self.y = y  
  
    def __sub__(self, other):  
        """ 
        重载减法运算,计算两点之差,实际上返回的表示该点到另一点的向量 
        :param other: 另一个点 
        :return: 该点到另一点的向量 
        """  
        return Point(self.x-other.x, self.y-other.y)  
  
  
class Segment:  
    def __init__(self, point1, point2):  
        self.point1 = point1  
        self.point2 = point2  
  
    def straddle(self, another_segment):  
        """ 
        :param another_segment: 另一条线段 
        :return: 如果另一条线段跨立该线段,返回True;否则返回False 
        """  
        v1 = another_segment.point1 - self.point1  
        v2 = another_segment.point2 - self.point1  
        vm = self.point2 - self.point1  
        if multiply(v1, vm) * multiply(v2, vm) <= 0:  
            return True  
        else:  
            return False  
  
    def is_cross(self, another_segment):  
        """ 
        :param another_segment: 另一条线段 
        :return: 如果两条线段相互跨立,则相交;否则不相交 
        """  
        if self.straddle(another_segment) and another_segment.straddle(self):  
            return True  
        else:  
            return False  
class Circle:
    def __init__(self,x,y,r):
        self.x,self.y,self.r = x,y,r
    def get_center_point(self):
        return Point(self.x,self.y)
"""
点在圆外返回0
点在圆上返回1
点在圆内返回2
"""
def is_point_out_circle(c,p):
    d = point_dis_pow(p,c.get_center_point())
    r2 = c.r**2
    if d > r2:
        return 0
    elif d == r2:
        return 1
    return 2
    
    
def jugde_circle_seg_cross(c,s):
    p1,p2 = s.point1,s.point2
    pos1 = is_point_out_circle(c,s.point1)
    pos2 = is_point_out_circle(c,s.point2)
    if pos1 == 1 or pos2 == 1:  # 任一点在圆上,相交
        return True
    if pos1 == 2 == pos2:  # 两点都在圆内,必不相交
        return False
    if pos1 != pos2:  # 一个点在内一个点在外,相交
        return True
    # 两点都在圆外,先求线段所在的直线方程:Ax+By+C=0
    # print('都在圆外')
    A = p1.y-p2.y
    B = p2.x-p1.x
    C = p1.x*p2.y-p2.x*p1.y
    # 点到直线的距离公式d = |Ax+By+c| // (A**2+B**2)**0.5
    x,y,r2 = c.x,c.y,c.r**2
    # print(x,y,r2,A,B,C)
    d1 = (A*x+B*y+C)**2
    d2 = (A*A+B*B)*r2
    # print(d1,d2)
    if d1 > d2:  # 如果圆心到直线的距离大于半径,不相交
        return False
    # 如果距离小于等于半径,则需要用余弦定理计算两个角是否是锐角,都是锐角则相交:否则不相交
    # cosA = (b^2+c^2-a^2)//(2*b*c)
    a2 = point_dis_pow(p1,c.get_center_point())
    b2 = point_dis_pow(p2,c.get_center_point())
    c2 = point_dis_pow(p2,p1)
    
    angleA = (b2+c2-a2)
    angleB = (a2+c2-b2)
    # print(angleA,angleB)
    if angleA>0 and angleB>0:  # 余弦为正是锐角
        return True
    
    return False
    
    
    
        
class Solution:
    def antPass(self, g: List[List[int]], path: List[List[int]]) -> List[bool]:
        n = len(g)
        uf = UnionFind(range(n))
        for i in range(n-1):
            for j in range(i+1,n):
                gi,gj = g[i],g[j]
                ni, nj = len(gi), len(gj)
                
                if ni == nj == 4:
                    a = Segment(Point(gi[0],gi[1]),Point(gi[2],gi[3]))
                    b = Segment(Point(gj[0],gj[1]),Point(gj[2],gj[3]))
                    if a.is_cross(b):
                        # print(i,j)
                        uf.union(i,j)
                elif ni == nj == 3:
                    if gi[2] < gj[2]:
                        gi,gj = gj,gi
                    r1,r2= gi[2],gj[2]
                    x1,y1,x2,y2 = gi[0],gi[1],gj[0],gj[1]
                    if (r1-r2)**2 <= (x1-x2)**2+(y1-y2)**2<=(r1+r2)**2:
                        # print(i,j)
                        uf.union(i,j)
                else:
                    if ni == 4:
                        gi,gj = gj,gi
                    if jugde_circle_seg_cross(Circle(gi[0],gi[1],gi[2]),Segment(Point(gj[0],gj[1]),Point(gj[2],gj[3]))):
                        # print(i,j)
                        uf.union(i,j)
                        
       
        return [uf.find_father(a) == uf.find_father(b) for a,b in path]
            
                    
                    
                        
        

六、参考链接

  • 链接: https://mp.csdn.net/mp_blog/creation/success/125129040

你可能感兴趣的:(力扣周赛复盘,leetcode,算法,职场和发展)