AcWing 算法基础课学习记录(Python,备战蓝桥杯)Day1 - Day30

 备战蓝桥杯学习路线:AcWing算法基础课->AcWing蓝桥杯课

(由于基础课和蓝桥课一共有85小时,现在每天平均是30mins到45mins,可能不是很够。从明天开始,每天看视频讲解一小时并且要消化内容,估计一起得花3-4小时。这样子差不多一月底,二月初可以结束视频课程)

Day1.(2021.10.12)

#Python input是输入的字符串,当要输入一串数组需要用如下arrs的两种方法
#如果输入的字符串没有用空格分开,则可以不加split()
n = int(input())
arrs = list(map(int, input().split()))
arrs = [int(x) for x in input().split()]

学习思路:1 先理解算法主要思想,2 背模板,3多做几道题(在做题的时候,当你AC了,马上把代码删除,再重新写一边,重复两三次)

快速排序

其主要思想是分治

1. 确定分界点de,一般选择左端点q[l],右端点q[r]或者中间q[(l+r)/2]

2. 调整范围,将左边调整为小于等于分界点,右边调整为大于等于分界点

3. 递归处理左右两段

其中重难点是2,简单方法(但耗空间)是:

1. 先设置两个数组a[], b[]

2. 数组q[l~r]中小于等于de的放入a[],大于等于de的放入b[]

3. 然后将a和b合并放入数组q,a在左边,b在右边

主要方法是,在数组两端分别设置指针i,j,while q[i]<de: i++,while q[j]>de: j--,if i<j swap(q[i],q[j]) else 递归处理左边部分(l,j)和右边部分(j+1,r)。重复上述步骤。

Python模板:

# acwing.785 快速排序

n = int(input())
a = list(map(int, input().split()))
#a = [int(x) for x in input()]

def quick_sort(a,l,r):
    if l>=r: return

    # x = a[l]
    x= max(min(a[l], a[r]), a[(l + r) // 2])
    i,j = l-1,r+1

    while i<j:
        i+=1
        j-=1
        while a[i]<x: i+=1
        while a[j]>x: j-=1
        if i<j: a[i],a[j] = a[j],a[i]

    quick_sort(a,l,j)
    quick_sort(a,j+1,r)

quick_sort(a,0,n-1)
print(' '.join(map(str,a)))

Day2.(2021.10.14)

Python Append

  • 当Append一个元素时,Python将创建一个足够大的列表,来容纳N个元素和将要被追加的元素。重新创建的列表长度大于N+1(虽然我们只触发一次append操作),实际上,为了未来的Append操作,M个元素长度(M>N+1)的内存将会被额外分配,然后,旧列表中的数据被copy到新列表中,旧列表销毁。
  • 额外内存的分配,只会发生在第一次Append操作时,当我们创建普通列表时,不会额外分配内存。

从这里还有网上其他地方整理知识点可以知道:

Append操作会申请新的一连串的空间,整体copy过去,那么就存在时间效率的下降,相对于一开始申请很大的空间。 

生成列表的五种方式(从0开始生成含有n个数的列表)

import timeit

def test1():
    l = []
    for i in range(1000):
        l += [i]

def test2():
    l = []
    for i in range(1000):
        l.append(i)

def test3():
    l = [i for i in range(1000)]

def test4():
    l = list(range(1000))

def test5():
    l = [0] * 1000

t1 = timeit.Timer("test1()", "from __main__ import test1")
print(t1.timeit(number=1000), "milliseconds")

t2 = timeit.Timer("test2()", "from __main__ import test2")
print(t2.timeit(number=1000), "milliseconds")

t3 = timeit.Timer("test3()", "from __main__ import test3")
print(t3.timeit(number=1000), "milliseconds")

t4 = timeit.Timer("test4()", "from __main__ import test4")
print(t4.timeit(number=1000), "milliseconds")

t5 = timeit.Timer("test5()", "from __main__ import test5")
print(t5.timeit(number=1000), "milliseconds")

这个结果和书上的结果有出入的地方在于1和2项,书上1的性能比2慢一个数量级(10倍)。

 Python数据结构性能分析

归并排序

归并排序的主要思想也是分治,它和快排的思想不同,快排是边分边治,它是先分后治。

1. 确定分界点 mid=(r+l)/2

2. 递归排序左右两部分 (根据选出的分界点,将分界点左边的递归处理,右边的递归处理)

3. 归并-合二为一 (递归的终止条件是l>=r,返回后就要考虑怎么把左右两部分正确的合在一起)

# acwing.787 归并排序

n = int(input())
a = list(map(int, input().split()))

def merge_sort(a,l,r):
    if l>=r:return

    mid = (l+r)//2
    merge_sort(a,l,mid)
    merge_sort(a,mid+1,r)

    tmp,i,j = [],l,mid+1
    while i<=mid and j<=r:
        if a[i]<a[j]:
            tmp.append(a[i])
            i+=1
        else:
            tmp.append(a[j])
            j+=1
    # or using tmp.extend(a[i:mid+1])
    if i<=mid:tmp+=a[i:mid+1]
    if j<=r:tmp+=a[j:r+1]
    a[l:r+1] = tmp[:]

merge_sort(a,0,n-1)
print(" ".join(map(str, a)))

Day3.(2021.10.14)

Day2的List五种生成方式和数据结构分析是今天做的。本来说把归并的788题做了,结果进去就跳转到算法基础课的活动,然后在leetcode上也没找到合适的题就把归并和快排复习了下,发现归并都是用<=或>=,而快排只有最开始判断的时候用了>=,其余都没有>=或<=,如果有的话还会报错。

Day4.(2021.10.15)

二分

这一节在刚开始听的时候还有点蒙蔽,一上来就在讲方法和思想,我连算法要解决的问题都还不清楚😂。然后二分的本质不是单调性,而是边界。比如:给定一个区间,在这个区间中定义了某种性质,使得这个性质在有左半边区间是满足的,而右半部不满足(注意两者没有交点,当整数二分时),则二分就可以寻找到这个边界左半边的边界和右半的都可以找到。而找到左边的边界,和右边的边界就分别是用两个不同的模板了。

这里解决的算法问题是假定先找黑色那个点,后找绿色那个点,然后整体思路就是:

找黑色的边界点,首先检查中间值是否满足黑色性质,

如果满足,则在mid右边部分找[mid,r], l=mid,注意是包含mid。否则mid左边找[l,mid-1],r=mid-1

找绿色的边界点,首先检查中间值是否满足绿色性质,

如果满足,则在mid的左边部分找[l,mid],r=mid。否则mid右边找[mid+1,r],l=mid+1

整数二分(ACWING789题):

注意:在找右边边界时需要mid=(l+r+1)/2 (如果不加1,假设l=r-1,如果此时True会无限递归),左边是mid=(l+r)/2。(或者记个口诀,let it go,l=mid的上面是有+1)

模板:解决单调数组找一个数最初和最后出现的位置,未出现则返回-1 -1

最后l和r的结果相等(当执行完一次寻找后,while)

# acwing 789.数的范围

if __name__ == '__main__':
    n, q = map(int, input().split())
    a = [int(x) for x in input().split()]

    for i in range(q):
        k = int(input())
        l, r = 0, n-1

        while l<r:
            mid = (l+r)//2
            if a[mid]>=k: r=mid
            else: l=mid+1
        if a[l]!=k:
            print('-1 -1')
            continue
        else:
            print(l, end=' ')

        l, r = 0, n-1
        while l<r:
            mid = (l+r+1)//2
            if a[mid]<=k: l=mid
            else: r=mid-1
        print(l)

Day5.(2021.10.17)

说实话,二分算法的过程我感觉很抽象,必须要依靠画图才能肯定。

当你找左边界,是找所有>=num的数,如果arr[mid]>=num,那么答案一定在左半边,那么r=mid,因为也有可能就是mid。

假如红色点是左边界,现在mid在黄色点,是>=num(红色点的值)的,则应该去左边找,则令右边界为mid。

浮点数二分:

浮点数二分相对于整数二分就简单许多,不需要有加一减一等操作,只需要判断要找的数在mid左边还是右边。

# acwing 790. 数的三次方根
n=float(input())
l,r=-10000,10000

while l<r-1e-8:
    mid=(l+r)/2
    if mid**3>=n: r=mid
    else: l=mid
print("{:.6f}".format(l))

Day6.(2021.10.18)

高精度:

Java和Python不需要关注高精度,Java有大整数类,Python本身就是无限大。但万一以后搞C++去了呢?

大数运算一般有A+B, A-B, A*a, A/a。其中大写代表大数(位数在1000000之内),小写代表小数(值效于10000)。解决这几个问题一般是模拟人工加法的过程:

当然首先要看的是怎么存储两个大数,要用数组来存储,而且是数组的低位存大数的低位,假如123456789为大数,则arr[0]=9, arr[1]=8 ... arr[8]=1。这是因为高位可能要进位,这样进位后不需要移动数组。

高精度加法:

# acwing 791. 高精度加法

n = list(map(int, input()[::-1]))
m = list(map(int, input()[::-1]))

def add(n,m):
    res = []
    i,j,t = 0,0,0
    while i<len(n) or j<len(m):
        if i<len(n):
            t+=n[i]
            i+=1
        if j<len(m):
            t+=m[j]
            j+=1
        res.append(t%10)
        t//=10
    if t: res.append(t)
    return res

res = add(n,m)[::-1]

print("".join(map(str,res)))

Day7.(2021.10.19)

高精度减法:

减法也是模拟人工减法的过程,同时减法是要保证A比B大的,不然就要B-A,然后加个负号(因此需要首先判断谁大)我们这里先假定A和B都是正数 (题目说了)。如果有负数,则可以用|A|+|B|或者|A|-|B|解决。(减法比加法复杂一点,因为需要判断谁大,和消除前导0)

# acwing 792. 高精度减法

n = list(map(int, input()[::-1]))
m = list(map(int, input()[::-1]))

def compare(n,m):
    if len(n) != len(m):
        return len(n)>len(m)
    i = len(n)-1
    while i>=0:
        if n[i]!=m[i]:
            return n[i]>m[i]
        i-=1
    # print(0) 这样子还可以节省时间
    # exit(0)
    return True

def sub(n,m):
    res = []
    i,j,t = 0,0,0

    while i<len(n) or j<len(m):
        if i<len(n):
            t+=n[i]
            i+=1
        if j<len(m):
            t-=m[j]
            j+=1
        res.append((t+10)%10)
        if t<0: t=-1
        else: t=0
    while len(res)>1 and res[-1]==0:
        res.pop()
    return res

if compare(n,m):
    res = sub(n,m)[::-1]
    print("".join(map(str,res)))
else:
    res = sub(m,n)[::-1]
    print("-" + "".join(map(str,res)))

Day8.(2021.10.20)

今天复习了下加法和减法的算法并根据Python的特性重构了下,并自己试验几组数组

高精度乘法:

这里的高精度乘法是是大数和小数相乘。主要思想是把小数b看成一个整体,用A去乘(A每位去乘)

# acwing 793. 高精度乘法

A = list(map(int, input()))[::-1]
b = int(input())

def mul(A,b):
    res,i,t,n = [],0,0,len(A)
    while i<n:
        t += A[i]*b
        res.append(t%10)
        t //= 10
        i += 1
    while t:
        res.append(t%10)
        t //= 10
    while len(res)>1 and res[-1]==0:
        res.pop()
    return res

res = mul(A,b)[::-1]
print("".join(map(str,res)))

Day9.(2021.10.21)

高精度除法:

这里也是大数除以小数,具体做法和手算除法一样。(从最高位开始算,但由于题目一般都是加减乘除要结合起来,所以也将除法的高位存在数组的高位)

# acwing 794. 高精度除法

A = list(map(int, input()))[::-1]
b = int(input())

def div(A,b):
    res, n, i, t= [], len(A), len(A)-1, 0

    while i>=0:
        t = t*10+A[i]
        res.append(t//b)
        t %= b
        i -= 1
    while len(res)>1 and res[0]==0:
        res.pop(0)
    return res, t

res, t = div(A,b)
print("".join(map(str, res)))
print(t)

Day10.(2021.10.22)

前缀和:

它能够快速求出数组中某一段的和。令s[0]=0可以少一个if判断(统一判断格式)。

C++输入数据大的时候用scanf,小的时候用cin,ios::sync_with_stdio(false)可以加快cin输入。

一维前缀和其实很简单,不多说

# acwing 795. 前缀和

n,m = map(int, input().split())
a = [0] + list(map(int, input().split()))
s = [0]*(n+1)

for i in range(1,n+1):
    s[i] = s[i-1] + a[i]

for _ in range(m):
    l,r = map(int, input().split())
    print(s[r]-s[l-1])

 二维前缀和

二维前缀和求某个矩形区域的和看上图理解下,值得注意的是初始化的时候就是将求某个区域(原数组)的前缀和改成求某个点的前缀和。

二维前缀和主要有python二维数组的定义和输入方式,同样要注意的是为了子矩阵和计算方便,它使得数组第一行和第一列都为0。

# 这个代码和上面一维的风格有点不同,主要是有没有新增一个数组来作为前缀和数组
# 数组长度为n,m列,q个询问
n, m, q = map(int, input().split())

#构造原数组
a = [[0] * (m+1) for _ in range(n+1)]
for i in range(1,n+1):
    a[i][1:] = list(map(int, input().split()))

#构造前缀和数组
#本来应该新建一个二维数组s,这样直接在a上原地修改可以节省空间,但可读性较差点
for i in range(1,n+1):
    for j in range(1,m+1):
        a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1]

for _ in range(q):
    x1, y1, x2, y2 = map(int, input().split())
    ans = a[x2][y2] - a[x1-1][y2] - a[x2][y1-1] + a[x1-1][y1-1]
    print(ans)

将一维前缀和风格改变:

# 数组长度为n,m个询问查询
n, m = map(int, input().split())

#构造输入数组
a = [0] * (n+1)
a[1:n+1] = list(map(int, input().split()))

#构造前缀和数组
for i in range(1,n+1):
    a[i] += a[i-1]

#l,r分别代表查询的左边界和右边界
for i in range(m):
    l, r = map(int, input().split())
    print(a[r]-a[l-1])

Day11.(2021.10.23)

一维差分:

假定a: a1,a2,a3...,an是数组b: b1,b2,b3...,bn的前缀和数组,那么成b是a的差分数组。即ai = b1+b2+b3+...+bi。(反过来可以推出b1=a1,b2=a2-a1,b3=a3-a2...,bn=an-an-1)(差分其实就是前缀和的逆运算)

差分有什么用呢,首先可以在O(n)的时间内,由b数组得到a数组,重点:让数组a某段(l,r)每个元素都加上(连续加多次)某一值的操作从O(n)->O(1),只需要将b[l]+c,b[r+1]-c。

首先我们要考虑如何初始化差分数组,然而对于差分数组来说,我们不需要单独考虑如何构造,只需要考虑如何更新(把初始化也当成更新)。

重点:我们来看下为什么可以把初始化也当成更新操作,首先你假设a数组全为0,则差分数组b也全为0。现在你要做的更新操作就是在数组a[1,1],[2,2],[3,3]...的范围依加上a1,a2,a3...,则相当于在数组b[1]+a1,b[2]-a1、b[2]+a2,b[3]-a2、...

# 数组长度为n,m个询问查询
n, m = map(int, input().split())

# 定义插入函数
def insert(l,r,c):
    b[l]+=c
    b[r+1]-=c

#构建原数组和差分数组
a = [0]*(n+2)
b = [0]*(n+2)

# 初始化原数组
a[1:n+1] = list(map(int, input().split()))

# 初始化差分数组
for i in range(1,n+1):
    insert(i,i,a[i])

for i in range(m):
    l, r, c = map(int, input().split())
    insert(l,r,c)

for i in range(1,n+1):
    b[i]+=b[i-1]
    print(b[i], end=" ")

二维差分:

注意二维差分和二维前缀和的不同,二维前缀和是对给定点的左上角操作,而二维差分则是对右下角进行操作。如果记不清楚该加减的位置,可以画图,这样更直观准确。(这里可能不好理解初始化二维差分数组的操作,插入操作是指在原数组某一范围插入一个数而要改变差分数组的哪些值的操作,当你在模拟在原始数组的每个点插入相对应的数时(这个动作是在改变差分数组的值),就相当于初始化差分数组)

# 数组长度为n,m. q个询问查询
n, m, q = map(int, input().split())

#构建原数组和差分数组
a = [[0]*(m+2) for _ in range(n+2)]
b = [[0]*(m+2) for _ in range(n+2)]

#定义插入函数
def insert(x1,y1,x2,y2,c):
    b[x1][y1] += c
    b[x1][y2+1] -= c
    b[x2+1][y1] -= c
    b[x2+1][y2+1] += c

#初始化原数组
for i in range(1,n+1):
    a[i][1:m+1] = list(map(int,input().split()))
#初始化差分数组
for i in range(1,n+1):
    for j in range(1,m+1):
        insert(i,j,i,j,a[i][j])

#q个插入操作
for i in range(0,q):
    x1,y1,x2,y2,c = map(int, input().split())
    insert(x1,y1,x2,y2,c)

#通过前缀和操作求原数组当前值
for i in range(1,n+1):
    for j in range(1,m+1):
        b[i][j] +=
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值