算法基础课-第二章复盘

一、栈

(一)单调栈

  • 特点:栈内元素以递增或递减的形式排序。
  • 应用:求解下一个更大元素、下一个更小元素、最大子数组和等问题。
  • 题目示例:

  • 代码逻辑:1、采用递增排序的单调栈,栈顶元素是栈内最大元素。2、循环读入列表元素,在单调栈非空的情况下,如果栈顶元素大于当前元素,将栈顶元素弹出栈,直到栈空或者栈顶元素小于当前元素。3、判断是栈空还是找到了小于当前元素的前序栈中元素。若栈空tt=-1,表明前面没有比当前元素小的。4、将当前元素压入栈中。
def main():
    N=100010;s=[0]*N;tt=-1;ans=[]
    n=int(input())
    nums=list(map(int,input().split()))
    for num in nums:
        while tt>-1 and num<=s[tt]:
            tt-=1
        if tt==-1:
            ans.append(-1)
        else:
            ans.append(s[tt])
        tt+=1;s[tt]=num
    print(" ".join(map(str,ans)))   
main()

(二)表达式求值

  • 数据结构:操作符栈op,操作数栈num,字典priority存放操作符的优先级。
  • 方法:1、循环读入串中字符,利用Python中的isdigit方法判断当前字符是否为数字字符,若是数字字符则循环判断累加数值。2、若非数字字符,则为操作符:①若为左括号,无条件压入。②若为右括号,在操作符栈非空且栈顶元素不是左括号的情况下,不断弹出操作符和操作数进行运算。最后将栈顶的左括号弹出栈(在一切操作合法的前提下)。③若为其他操作符,在栈非空且栈顶元素不是左括号的前提下,如果当前读入的操作符的优先级小于等于操作符栈顶元素的优先级,则不断弹出操作符和操作数进行运算。最后将当前读入的操作符压入操作符栈中。③检查操作符栈是否为空,若非空,弹出操作符和操作数进行计算。④最终操作数栈顶元素就是最终值。
  • 题目

from collections import deque
op=deque();num=deque()
priority={'+':0,'-':0,'*':1,'/':1}
def cal():
    global op,num
    ins=op.pop()
    b=num.pop()
    a=num.pop()
    if ins=='+':
        a+=b
    elif ins=='-':
        a-=b
    elif ins=='*':
        a*=b
    else:
        a=int(a/b)
    num.append(a)
def main():
    char=input();i=0
    while i<len(char):
        if char[i].isdigit():
            res=0
            #比如1+23456,其中23456就需要如下操作得到
            while i<len(char) and char[i].isdigit():
                res=res*10+int(char[i])
                i+=1
            #i回退一位,防止后续字符被越位
            i-=1
            num.append(res)
        else:
            if char[i]=='(':
                op.append(char[i])
            elif char[i]==')':
                while op and op[-1]!='(':
                    cal()
                op.pop()
            else:
                while op and op[-1]!='(' and priority[char[i]]<=priority[op[-1]]:
                    cal()
                op.append(char[i])
        i+=1
    while op:
        cal()
    print(num[-1])
main()

二、滑动窗口

  • 实现:使用动态队列实现
  • 应用:
  1. 最大/最小子数组问题:例如,找出数组中和最大的连续子数组(最大子数组和问题)。
  2. 窗口内元素的统计:比如,给定一个数组和两个整数 k 和 t,找出数组中是否存在长度为 k 的连续子数组,使得子数组内所有元素的和至少为 t。
  3. 满足特定条件的子数组:例如,找出数组中所有连续子数组,使得子数组内所有元素的乘积不超过给定的阈值。
  4. 字符串问题:比如,找出字符串中最长的不含有重复字符的子串。
  5. 数据流问题:在处理数据流时,滑动窗口可以用来计算最近一段时间内的统计数据,如移动平均值。
  6. 时间序列分析:在时间序列数据中,滑动窗口可以用来分析一段时间内的趋势或周期性变化。
  • 题目

方法:比如要输出每个窗口内的最小值,就创建一个递增的动态队列,每个队列的队头元素就是每个窗口内的最小值的索引(需要维护边界,保证每个队列的队头是在窗口的左窗台以内)。

代码:

def main():
    n,k=map(int,input().split())
    nums=list(map(int,input().split()))
    q=[0]*1001000
    hh=0;tt=-1;i=0
    while i<n:
        while hh<=tt and q[hh]<i-k+1:
            hh+=1
        while hh<=tt and nums[q[tt]]>=nums[i]:
            tt-=1
        tt+=1;q[tt]=i
        if i-k+1>=0:
            print(nums[q[hh]],end=" ")
        i+=1
    print()
    hh=0;tt=-1;i=0
    while i<n:
        while hh<=tt and q[hh]<i-k+1:
            hh+=1
        while hh<=tt and nums[q[tt]]<=nums[i]:
            tt-=1
        tt+=1;q[tt]=i
        if i-k+1>=0:
            print(nums[q[hh]],end=" ")
        i+=1
main()

三、KMP

  • 应用:字符串匹配,返回匹配的下标值
  • 主要思想:
  1. 预处理阶段(构建next数组)

    • 构建next数组,存储模式字符串的前缀和后缀的最长公共元素长度。前缀是指字符串开头到某个位置的子串,后缀是指字符串末尾到某个位置的子串。
    • 能够根据next数组的信息,决定模式字符串应该向右滑动多少位。
  2. 字符串匹配阶段

    • 算法会使用next数组来指导模式字符串在文本字符串中的滑动。当模式字符串的某个字符与文本字符串的对应字符不匹配时,算法会查看部分匹配表,根据表中存储的信息,将模式字符串向右滑动适当的位数,而不是简单地滑动一位。
    • 通过这种方式,KMP算法能够减少不必要的比较,提高字符串搜索的效率。
def main():
    N=100100;ne=[0]*N
    n=int(input())
    #第一个字符对应的下标变成1
    p=[x for x in input()];p=[0]+p
    m=int(input())
    s=[x for x in input()];s=[0]+s
    i=2;j=0
    while i<=n:
        while j and p[i]!=p[j+1]:
            j=ne[j]
        if p[i]==p[j+1]:
            j+=1
        ne[i]=j
        i+=1
    i=1;j=0
    while i<=m:
        while j and s[i]!=p[j+1]:
            j=ne[j]
        if s[i]==p[j+1]:
            j+=1
        if j==n:
            print(i-j,end=" ")
            j=ne[j]
        i+=1
main()

四、Trie树

  • 数据结构:子节点指针数组son,子结点计数数组cnt,结点标记idx。
  • 应用:字符串检索。
  • 性质:
  1. 根节点不包含字符:除根节点外,每一个节点都只包含一个字符。‌
  2. 路径表示字符串:从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
  3. 子节点字符唯一性:每个节点的所有子节点包含的字符都不相同。‌

(一)字符串统计

题目:

思想:从根结点(标记为0)开始追溯,p表示当前结点。对于插入操作,循环读入字符,如果当前结点p的子节点指针数组son中并没有指向当前字符的指针(即son[p][u]=0,其中u可以由当前字符ASCII码减去'a'的ASCII码得到),那么创建结点(idx+=1,son[p][u]=idx,通过idx来标识结点)。如果已存在指针,p指向当前字符的结点。

代码:

N=100100;son=[[0]*26 for _ in range(N)]
cnt=[0]*N;idx=0
def insert(char):
    global son,cnt,idx
    p=0
    for ch in char:
        u=ord(ch)-ord('a')
        if not son[p][u]:
            idx+=1;son[p][u]=idx
        p=son[p][u]
    cnt[p]+=1
def query(char):
    global son,cnt,idx
    p=0;flag=True
    for ch in char:
        u=ord(ch)-ord('a')
        if not son[p][u]:
            flag=False;break
        p=son[p][u]
    if flag:
        print(cnt[p])
    else:
        print(0)
def main():
    global son,cnt,idx
    n=int(input())
    for _ in range(n):
        op,char=input().split()
        if op=='I':
            insert(char)
        else:
            query(char)
main()

(二)最大异或对

题目:

思路: 1、先用Trie树存储每个树的二进制表达,从最高位开始存储。2、再逐个比较得到的每个数的最大异或值,其中最大的数即为输出:初始化res=0,p指向根结点(p=0)。从最高位开始计算,如果存在相应位置i上与该位不同的值(比如0和1),则res加上1左移i位(1<<i),当前结点指向异或结点。

代码:

N=3000000;s=[[0]*2 for _ in range(N)];idx=0
def insert(num):
    global s,idx
    p=0
    for i in range(30,-1,-1):
        x=num>>i&1
        if not s[p][x]:
            idx+=1;s[p][x]=idx
        p=s[p][x]
def query(num):
    global s,idx
    p=0;res=0
    for i in range(30,-1,-1):
        x=num>>i&1
        if s[p][1-x]:
            res+=1<<i
            p=s[p][1-x]
        else:
            p=s[p][x]
    return res
def main():
    n=int(input())
    nums=list(map(int,input().split()))
    for num in nums:
        insert(num)
    res=0
    for num in nums:
        res=max(res,query(num))
    print(res)
main()

五、并查集

  • 数据结构:祖宗结点数组p
  • 核心思想:1、初始化:每个结点的祖宗结点都是自身,以祖宗结点代表一个集合。2、并集:将其中一个集合的祖宗结点改成另外一个结点的祖宗结点。
  • 核心代码:
def find(x):
    if x!=p[x]:
       p[x]=find(p[x])
    return p[x]
  • 题目:食物链

方法:1、利用并查集,将有关系的动物(吃与被吃,同类关系)纳入一个集合中,利用不同动物与祖宗结点之间的距离来标识关系(如果1吃2,则(d[1]-d[2]-1)%3=0;如果1和2是同类那么(d[1]-d[2])%3=0)。2、距离说明:①初始化距离全为0。②构建距离关系:(1)如果x与y是同类,目前尚未在同一个集合中,则将x的祖宗结点px改成y的祖宗结点py,即p[px]=py。已知d[x]是x到px的距离,d[y]是y到py的距离。由于将px与py相连,x与y是同类,d[px]=d[y]-d[x](实际上是(d[y]-(d[px]+d[x]))%3=0,移项后可得到该公式。(2)如果x吃y,同理更改祖宗结点后,d[px]=d[y]-d[x]+1③判断距离关系(真话假话判断)

 

代码:

N=50010;p=[0]*N;d=[0]*N
def find(x):
    global p,d
    if x!=p[x]:
        root=find(p[x])
        d[x]+=d[p[x]]
        p[x]=root
    return p[x]
def main():
    global p,d
    n,k=map(int,input().split())
    res=0
    for i in range(1,n+1):
        p[i]=i
    for _ in range(k):
        op,x,y=map(int,input().split())
        if x>n or y>n:
            res+=1
            continue
        if op==1:
            px=find(x);py=find(y)
            if px==py:
                if (d[x]-d[y])%3!=0:
                    res+=1
            else:
                p[px]=py
                d[px]=d[y]-d[x]
        elif op==2:
            if x==y:
                res+=1
                continue
            else:
                px=find(x);py=find(y)
                if px==py:
                    if (d[x]-d[y]-1)%3!=0:
                        res+=1
                else:
                    p[px]=py
                    d[px]=d[y]-d[x]+1
    print(res)
main()

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值