Acwing算法基础课学习笔记

第一章 基础算法

快速排序

给定你一个长度为 n 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼ 1 0 9 10^9 109 范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围:1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5


解析:

  1. 找到一个值x作为划分点,这个值可以是q[l],q[r], q[l+r/2],也可以是区间的随机值
  2. 定义两个指针i,j,分别指向区间的两侧
  3. 当指针i指向的值q[i] > x时,指针i向右移动一位;指针j指向的值q[j] > x,指针j向左移动一位
  4. 对于任意时刻,都存在q[l,i] 的值小于等于x,区间q[j,r]的值大于等于x
  5. 当i和j各找到一个不符合条件的值的时候,交换i,j指向的值
  6. 递归对两个子区间进行排序
    代码:
class Quick_sort:
    def qs(self,nums,l,r):
        if l == r:return # 当区间为1时,退出递归
        x = nums[l + r >> 1] # 选择一个用于划分区间的值
        i = l - 1 # 将左指针指向l的左侧
        j = r + 1 # 将右指针指向r的右侧
        while i < j:
            i += 1 # 先向左移动一次i;目的:交换i和j的值后不需要单独移动一次i和j
            while nums[i] < x: i += 1 # 当i指向的值不小于x的时候,停止循环
            j -= 1 # 先向右移动一次j
            while nums[j] > x: j -= 1 # 当j指向的值不大于x的时候,停止循环
            if i < j: # i小于j的时候才进行交换,此时找到的值就满足nums[i] >= x和nums[j] <= x
                nums[i], nums[j] = nums[j], nums[i]
        # 递归处理子区间
        # 此处选择区间存在边界情况:
        # 1.如果选择[l,j][j+1,r]时,x的取值就不能包含右端点;x = nums[l + r >> 1]
        # 2.如果选择[l,i-1][i,r]时,x的取值就不能包含左端点;x = nums[l + r + 1 >> 1]
        self.qs(nums,l,j) 
        self.qs(nums,j+1,r)

    def main(self):
        n = int(input())
        self.line = list(map(int,input().split()))
        self.qs(self.line,0,n-1)
        for i in self.line:
            print(i, end=' ')

if __name__ == '__main__':
    quicksort = Quick_sort()
    quicksort.main()

给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。

输入格式
第一行包含两个整数 n 和 k。
第二行包含 n 个整数(所有整数均在 1∼ 1 0 9 10^9 109 范围内),表示整数数列。

输出格式
输出一个整数,表示数列的第 k 小数。

数据范围
1≤n≤100000,
1≤k≤n

输入样例:
5 3
2 4 1 5 3

输出样例:
3


解析:

  1. 找到一个值x作为划分点,这个值可以是q[l],q[r], q[l+r/2],也可以是区间的随机值
  2. 定义两个指针i,j,分别指向区间的两侧
  3. 当指针i指向的值q[i] > x时,指针i向右移动一位;指针j指向的值q[j] > x,指针j向左移动一位
  4. 对于任意时刻,都存在q[l,i] 的值小于等于x,区间q[j,r]的值大于等于x
  5. 当i和j各找到一个不符合条件的值的时候,交换i,j指向的值
  6. 计算左区间的长度:sl = j - l + 1 (或者:l = i - l)
  7. 如果k小于等于sl,则第k小的数在左区间,递归处理左区间即可;反之递归处理右区间
class Quickcheck:
    def quickcheck(self,l,r,k):
        if l == r:return self.nums[l] # 当区间只剩下一个数的时候,这个数就是第k小的数

        x = self.nums[l + r >> 1] # 选取任意一个值作为分界点,小于这个值的点划分到左区间,大于这个值的点划分到右区间
        i,j = l-1,r+1
        while i < j:
            i += 1
            while self.nums[i] < x:i += 1
            j -= 1
            while self.nums[j] > x:j -= 1
            if i < j:self.nums[i],self.nums[j] = self.nums[j],self.nums[i]
        # 左区间长度为j - l + 1,如果选则[l,i-1][i,r]作为划分点的话,左区间的长度就为sl = i - l(此时x不能取到左端点)
        sl = j - l + 1
        if sl >= k: return self.quickcheck(l,j,k)
        else:return self.quickcheck(j+1,r,k-sl)

    def main(self):
        n,k = map(int,input().split())
        self.nums = list(map(int,input().split()))
        print(self.quickcheck(0,n-1,k))

if __name__ == '__main__':
    quickcheck = Quickcheck()
    quickcheck.main()

归并排序

给定你一个长度为 n 的整数数列。请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1109 范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000

输入样例:
5
3 1 2 4 5

输出样例:
1 2 3 4 5


解析:

  1. 找到区间的中点mid = l + r >> 1
  2. 对左右区间进行递归排序
  3. 定义两个指针i,j,分别指向左右区间的起点
  4. 对两个区间的每个值进行比较大小,需要定义一个额外的结果数组,用来存放排序后的值
    • 当i指向的值小于j指向的值,就将i指向的值加入到结果数组中,并将指针i向后移动一位
    • 当j指向的值小于等于i指向的值,就将j指向的值加入到结果数组中,并将指针j想左移动一位
    • 对于任意时刻,指针j指向的值都满足大于等于i指向的值
  5. 判断i和j是否移动到子数组的末尾,如果没有移动到末尾,则说明子数组中存在剩余元素,直接加入到结果列表中即可
class Mergesort:
    def mergesort(self,nums):
        if len(nums) == 1:return # 当区间长度为1时直接返回
        mid = len(nums) >> 1 # 获取区间的中点
        L = nums[:mid] # 将当前区间以中点划分左右两个区间
        R = nums[mid:]
        self.mergesort(L) # 对左右区间进行递归排序
        self.mergesort(R)
        i = j = k = 0 # 定义三个指针,分别指向左区间、右区间、结果数组的起点
        while i < len(L) and j < len(R): # 边界条件
            if L[i] < R[j]: # 如果左区间的值小于右区间的值,就将左区间的值加入到结果数组中
                nums[k] = L[i]
                i += 1
            else: # 反之将右区间的值加入到结果数组中
                nums[k] = R[j]
                j += 1
            k += 1
        while i < len(L): # 判断左右区间是否还有剩余元素,处理剩余元素
            nums[k] = L[i]
            k += 1
            i += 1
        while j < len(R):
            nums[k] = R[j]
            k += 1
            j += 1
        return nums

    def main(self):
        int(input())
        nums = list(map(int,input().split()))
        res = self.mergesort(nums)
        for i in res:
            print(i, end=' ')

if __name__ == '__main__':
    mergesort = Mergesort()
    mergesort.main()

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,
如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。

输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。

输出格式
输出一个整数,表示逆序对的个数。

数据范围
1≤n≤100000,
数列中的元素的取值范围 [1, 1 0 9 10^9 109]。

输入样例:
6
2 3 4 5 6 1
输出样例:
5


解析:

  1. 找到区间的中点mid,mid将整个区间划分为[:mid][mid:]两个区间
  2. 分别递归处理两个区间
  3. 当逆序对a,b同时出现在左区间或者右区间,直接在递归处理的时候加上返回值即可
  4. 当逆序对a,b一个在左区间一个在右区间,需要分情况讨论
    • 当q[i] > q[j] 时,q[i]之后的所有数都对q[j]构成逆序对
class Reverse_order:
    def mergesort(self,nums):
        if len(nums) == 1:return 0 # 区间长度为1,这个区间就不存在逆序对
        mid = len(nums) >> 1 
        left = nums[:mid] # mid将原始区间划分为左右两个子区间
        right = nums[mid:]
        res = 0 
        res += self.mergesort(left) # 递归处理左右区间
        res += self.mergesort(right)
        i = j = k = 0
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:nums[k] = left[i]; k += 1; i += 1
            # 当left[i] > right[j]:说明left区间中[i:mid)的数都与right[j]构成逆序对
            else:nums[k] = right[j];k += 1;j += 1;res += mid - i
        while i < len(left):nums[k] = left[i]; i += 1;k += 1
        while j < len(right):nums[k] = right[j]; j += 1;k += 1

        return res

    def main(self):
        n = int(input())
        nums = list(map(int,input().split()))
        res = self.mergesort(nums)
        print(res)

if __name__ == '__main__':
    reverseorder = Reverse_order()
    reverseorder.main()

二分查找

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。

输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。

数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000

输入样例:
6 3
1 2 2 3 3 4
3
4
5

输出样例:
3 4
5 5
-1 -1


思路:
能够进行二分的充要条件是,区间存在二段性(不一定是单调性)

  1. 找到区间的中点mid
  2. 判断目标值落在哪个区间,更新区间的端点为目标区间,这样每次缩小区间长度为原来的一半
class Binary_search:
    def binarysearch(self,nums,target):
        """
        找到第一个大于等于目标值的索引值
        :param nums: 
        :param target: 
        :return: 
        """
        l,r = 0,len(nums) - 1
        while l < r:
            mid = l + r>> 1
            if nums[mid] >= target:r = mid
            else:l = mid + 1
        return l

    def binarysearch2(self,nums,target):
        """
        找到第一个小于等于目标值的索引值
        :param nums: 
        :param target: 
        :return: 
        """
        l,r = 0,len(nums) - 1
        while l < r:
            mid = l + r + 1>> 1
            if nums[mid] <= target: l = mid # 如果这里取的是l,mid计算就需要取l+r+1>>1
            else:r = mid - 1
        return r

    def main(self):
        n,q = map(int,input().split())
        nums = list(map(int,input().split()))
        for i in range(q):
            k = int(input())
            left = self.binarysearch(nums,k)
            right = self.binarysearch2(nums,k)
            if nums[left] != k:print(-1,-1)
            else:print(left,right)

if __name__ == '__main__':
    binarysearch = Binary_search()
    binarysearch.main()

前缀和与差分

AcWing 795. 前缀和(一维前缀和)

输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。

输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式
共 m 行,每行输出一个询问的结果。

数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000

输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例:
3
6
10


class Prefixsum:
    def main(self):
        n,m = map(int,input().split())
        nums = list(map(int,input().split()))
        nums = [0] + nums
        for i in range(1,n+1):
            nums[i] += nums[i-1]
        for j in range(m):
            l,r = map(int,input().split())
            print(nums[r] - nums[l-1])

if __name__ == '__main__':
    perfixsum = Prefixsum()
    perfixsum.main()

AcWing 796. 子矩阵的和(二维前缀和)


输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,
每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。

输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

输出格式
共 q 行,每行输出一个询问的结果。

数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000

输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:
17
27
21


解析:

  1. 二维前缀和: p r e f i x [ x 3 , y 3 ] = p r e f i x [ x 2 , y 3 ] + p r e f i x [ x 3 , y 2 ] − p r e f i x [ x 2 , y 2 ] + n u m s [ x 3 , y 3 ] prefix[x3,y3] = prefix[x2,y3] + prefix[x3,y2] - prefix[x2,y2] + nums[x3,y3] prefix[x3,y3]=prefix[x2,y3]+prefix[x3,y2]prefix[x2,y2]+nums[x3,y3]
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/782a7f424d4d471f8e7bb4cba7629ed5.png
    在这里插入图片描述
    在这里插入图片描述
  2. 二维区间和: p r e f i x [ x 2 , y 2 , x 4 , y 4 ] = p r e f i x [ x 4 , y 4 ] − p r e f i x [ x 4 , y 1 ] − p r e f i x [ x 1 ] [ y 4 ] + p r e f i x [ x 1 , y 1 ] prefix[x2,y2,x4,y4] = prefix[x4,y4] - prefix[x4,y1] - prefix[x1][y4] + prefix[x1,y1] prefix[x2,y2,x4,y4]=prefix[x4,y4]prefix[x4,y1]prefix[x1][y4]+prefix[x1,y1]

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Prefixsum_arr:
    def __init__(self):
        N = 1010
        self.prefix = [[0] * N for _ in range(N)]

    def prefixsum(self,nums,n,m):
        for i in range(1,n+1):
            for j in range(1,m+1):
                self.prefix[i][j] = self.prefix[i-1][j] + self.prefix[i][j-1] - self.prefix[i-1][j-1] + nums[i-1][j-1]

    def main(self):
        n,m,q = map(int,input().split())
        nums = []
        for i in range(n):
            line = list(map(int,input().split()))
            nums.append(line)
        self.prefixsum(nums,n,m)

        for j in range(q):
            x1,y1,x2,y2 = map(int,input().split())
            res = self.prefix[x2][y2] - self.prefix[x2][y1-1] - self.prefix[x1-1][y2] + self.prefix[x1-1][y1-1]
            print(res)

if __name__ == '__main__':
    prefixsum = Prefixsum_arr()
    prefixsum.main()

差分

AcWing 797. 差分(一维差分)

输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。

输出格式
共一行,包含 n 个整数,表示最终序列。

数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000

输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例:
3 4 5 3 4 2


解析:
差分:可以看做是前缀和的逆运算,
给定一个原数组a:a[1],a[2],a[3]...a[n];
构造一个数组b:b[1],b[2],b[3]...b[i];
使得 a[i] = b[1] + b[2]+ b[3] +...+ b[i]
a数组就是b数组的前缀和,b数组就是a数组的差分数组
b数组构造方式如下:

a[0] = 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] = a[3] - a[2];
...
b[n] = a[n] - a[n-1];
  • 差分数组的作用:
  • 给定区间[l,r],让我们把a数组中的[l,r]区间中的每一个数都加上c,暴力做法需要对区间进行遍历,再对区间[l,r]增加一个c
  • 差分数组,只需要在s[l] += c,s[r+1] -= c,就可使得原数组在区间[l,r]上增加c

class Differenc:
    def __init__(self):
        N = 100010
        self.nums = [0] * N # 差分数组
        
    def insert(self,l,r,c):
        # 在差分数组区间l加上c,区间r+1减去c,使得原数组在区间[l,r]内增加c
        self.nums[l] += c 
        self.nums[r+1] -= c

    def main(self):
        n,m = map(int,input().split())
        nums = list(map(int,input().split()))
        for i in range(1,n+1):
            self.insert(i,i,nums[i-1]) # 求差分数组
        for i in range(m):
            l,r,c = map(int,input().split())
            # 将差分数组的l位置增加c,r+1位置减去c
            self.nums[l] += c
            self.nums[r+1] -= c
        for i in range(1,n+1):
            # 对差分数组求一遍前缀和,得到原数组
            self.nums[i] += self.nums[i-1]
            print(self.nums[i],end=' ')

if __name__ == '__main__':
    difference = Differenc()
    difference.main()

AcWing 798. 差分矩阵(二维差分)
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。

输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。

输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000

输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:
2 3 4 1
4 3 4 1
2 2 2 2


解析:
与一维差分数组类似,要使得特定区间a[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,再对差分数组求一遍前缀和,就可得到原数组的区间a[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;
class Difference2:
    def __init__(self):
        N = 1010
        self.arr = [[0] * N for _ in range(N)] # 差分数组

    def insert(self,x1,y1,x2,y2,c):
        self.arr[x1][y1] += c
        self.arr[x1][y2+1] -= c
        self.arr[x2+1][y1] -= c
        self.arr[x2+1][y2+1] += c

    def main(self):
        n, m, q = map(int,input().split())
        for i in range(1,n+1):
            nums = list(map(int,input().split()))
            nums = [0] + nums
            for j in range(1,m+1):
                self.insert(i,j,i,j,nums[j])
        for i in range(q):
            x1, y1, x2, y2, c = map(int,input().split())
            self.insert(x1,y1,x2,y2,c)

        for i in range(1,n+1):
            for j in range(1,m+1):
                # 求二维数组的前缀和
                self.arr[i][j] += (self.arr[i-1][j] + self.arr[i][j-1] - self.arr[i-1][j-1])
                print(self.arr[i][j],end=' ')
            print()

if __name__ == '__main__':
    difference2 = Difference2()
    difference2.main()

AcWing 799. 最长连续不重复子序列

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。

输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105

输入样例:
5
1 2 2 3 5

输出样例:
3


思路:

  1. 定义两个指针i,j,i指针往右移动
  2. 开一个数组count用于存放每个数出现的频次,以数组中每个数的值为索引,数组count中的值为这个数出现的频次
  3. 当这个数的频次大于1,就说明当前数出现了重复次数。并且出现重复次数是因为当前i指向的元素的加入
  4. 向右移动指针j,直到频次大于1的元素的频次降到1
class Double_pointer:
    def __init__(self):
        N = 100010
        self.nums = [0] * N

    def main(self):
        n = int(input())
        nums = list(map(int,input().split()))

        j = res = 0
        for i in range(n):
            self.nums[nums[i]] += 1
            while self.nums[nums[i]] > 1:
                self.nums[nums[j]] -= 1
                j += 1
            res = max(res,i - j + 1)
        print(res)

if __name__ == '__main__':
    double = Double_pointer()
    double.main()

AcWing 800. 数组元素的目标和

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。

输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。

输出格式
共一行,包含两个整数 i 和 j。

数据范围
数组长度不超过 1 0 5 10^5 105
同一数组内元素各不相同。
1 ≤ 数组元素 ≤ 1 0 9 1≤数组元素≤10^9 1数组元素109

输入样例:
4 5 6
1 2 4 7
3 4 6 8 9

输出样例:
1 1


解析:

  1. 由于两个数组都是升序排列,可以定义两个指针i,j,指针i指向第一个数组的首位,j指向第二个数组的末尾
  2. 当i和j指向的两个数的和大于目标值,就将j向左移动一位
  3. 当i和j指向的两个数的和小于目标值,就将i向右移动一位
  4. 当i和j指向的两个数的和等于目标值,返回当前的i和j的值并结束循环
class Double_pointer2:
    def main(self):
        n, m, target = map(int,input().split())
        nums1 = list(map(int,input().split()))
        nums2 = list(map(int,input().split()))
        i = 0
        j = m - 1
        while i < n and j >= 0:
            if nums1[i] + nums2[j] == target:
                print(i,j)
                break
            elif nums1[i] + nums2[j] > target:
                j -= 1
            else:i += 1

if __name__ == '__main__':
    double2 = Double_pointer2()
    double2.main()

AcWing 2816. 判断子序列

给定一个长度为 n 的整数序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an以及一个长度为 m 的整数序列 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,,bm

请你判断 a 序列是否为 b 序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 a 1 , a 3 , a 5 {a_1,a_3,a_5} a1,a3,a5 是序列 a 1 , a 2 , a 3 , a 4 , a 5 {a_1,a_2,a_3,a_4,a_5} a1,a2,a3,a4,a5的一个子序列。

输入格式
第一行包含两个整数 n,m。
第二行包含 n 个整数,表示 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an
第三行包含 m 个整数,表示 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,,bm

输出格式
如果 a 序列是 b 序列的子序列,输出一行 Yes。
否则,输出 No。

数据范围
1 ≤ n ≤ m ≤ 1 0 5 , 1≤n≤m≤10^5, 1nm105,
− 1 0 9 ≤ a i , b i ≤ 1 0 9 −10^9≤a_i,b_i≤10^9 109ai,bi109

输入样例:
3 5
1 3 5
1 2 3 4 5

输出样例:
Yes


解析:

  1. 定义两个指针i,j,分别指向两个数组的首位置
  2. 如果指针j指向的值与指针i指向的值相等,就将指针i向后移动一位
  3. 当j移动到数组末尾时,i的值等于子串长度时,匹配成功,否则匹配失败
class Double_pointer:
    def main(self):
        n, m = map(int,input().split())
        nums1 = list(map(int,input().split()))
        nums2 = list(map(int,input().split()))

        i = 0
        for j in range(m):
            while i < n and nums2[j] == nums1[i]:
                i+= 1
        if i == n:print("Yes")
        else:print("No")

if __name__ == '__main__':
    dounle3 = Double_pointer()
    dounle3.main()

位运算

AcWing 801. 二进制中1的个数

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。

输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。

输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。

数据范围
1≤n≤100000,
0 ≤ 数列中元素的值 ≤ 1 0 9 0≤数列中元素的值≤10^9 0数列中元素的值109

输入样例:
5
1 2 3 4 5

输出样例:
1 1 2 1 2


解析:
算法1:lowbit()
原码(x): 十进制数据的二进制表现形式就是原码,原码最左边的一个数字就是符号位,0为正,1为负。
反码(~x):正数的反码是其本身(等于原码),负数的反码是符号位保持不变,其余位取反。
补码(-x):正数的补码是其本身,负数的补码等于其反码 +1。因为反码不能解决负数跨零(类似于 -6 + 7)的问题,
所以补码出现了。

  1. lowbit计算二进制表示中的最低位的1的位置
    原理:原码 & 补码 ,例如十进制12,原码为:0b1100;反码为:0b0100(即0b0011 + 1)
    0b1100 & 0b0100 = 0b0100
  2. 计算x中1的个数,当x不为0时,每次循环减去一个lowbit(x),循环的次数就是x的二进制表示中1的个数
class Count1:
    def lowbit(self,x):
        """
        获取x的二进制中最低位1
        :param x:
        :return:
        """
        return x & -x

    def main(self):
        n = int(input())
        nums = list(map(int,input().split()))
        for i in range(n):
            res = 0
            k = nums[i]
            while k:
                k -= self.lowbit(k)
                res += 1
            print(res,end=' ')

if __name__ == '__main__':
    count1 = Count1()
    count1.main()
class Count2:
    def main(self):
        n = int(input())
        nums = list(map(int,input().split()))
        for i in range(n):
            res = 0
            k = nums[i]
            while k:
                res += k & 1
                k >>= 1
            print(res,end=' ')

if __name__ == '__main__':
    count2 = Count2()
    count2.main()

离散化

AcWing 802. 区间和

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。

输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。

输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。

数据范围
− 1 0 9 ≤ x ≤ 1 0 9 , −10^9≤x≤10^9, 109x109,
1 ≤ n , m ≤ 1 0 5 , 1≤n,m≤10^5, 1n,m105,
− 1 0 9 ≤ l ≤ r ≤ 1 0 9 , −10^9≤l≤r≤10^9, 109lr109,
− 10000 ≤ c ≤ 10000 −10000≤c≤10000 10000c10000

输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:
8
0
5


解析:

  1. 将所有的插入和查询操作的索引值全部加入到一个索引数组中
  2. 对索引数组进行排序并去重,得到的新数组就是离散化后的数组
  3. 将插入操作和查询操作用一个二维数组存储,add数组存储的是每个插入操作的原始索引值和插入的数值,query数组存储的是每个查询操作的原始[l,r]值
  4. 开一个新的数组用来存放离散化后的值,这个值插入的位置就是离散化后的索引值对应的位置
  5. 查询操作处理类似,将查询操作的原始索引值转换为新的索引值,利用新的索引值在新的数组中去查找对应区间的值
  6. 离散化操作:由于离散化数组是排序去重的数组,存在单调性,可利用二分去查找大于等于当前值的第一个位置,查找出来的值可根据实际情况去确定是否需要做加1处理,这里由于是计算前缀和,前缀和的下标从1开始,所以这里将查询出来的下标直接加1
  7. 利用前缀和去计算区间和
class Discretization:
    def __init__(self):
        N = 300010 # 这里需要设置3倍空间,n次插入和m次查询的上界
        self.add = []
        self.query = []
        self.alls_index = []

        self.nums = [0] * N
        self.prefix = [0] * N

    def find(self,nums,x):
        """
        查找离散化后,新的索引值
        :param nums:离散化后的数组
        :param x:需要查询的索引值
        :return: 离散化后对应的新的索引值
        """
        l = 0
        r = len(nums) - 1
        while l < r:
            mid = l + r >> 1
            if nums[mid] >= x:r = mid
            else: l = mid + 1
        return l + 1 # 由于计算的前缀和,所以索引需要从1开始,这里返回的索引值加上了1

    def unique(self,nums):
        """
        对数组进行排序并去重
        :param nums: 待排序去重的数组
        :return: 排序去重后的数组
        """
        nums.sort()
        i = 1
        for j in range(1,len(nums)):
            if nums[j] != nums[j-1]:
                nums[i] = nums[j]
                i += 1
        return nums[:i]

    def main(self):
        n,m = map(int,input().split())
        for i in range(n):
            x,c = map(int,input().split())
            self.add.append([x,c])
            self.alls_index.append(x)

        for j in range(m):
            l,r = map(int,input().split())
            self.query.append([l,r])
            self.alls_index.append(l)
            self.alls_index.append(r)

        # 对所有的索引数组进行排序并去重
        index = self.unique(self.alls_index)

        # 处理插入操作,并将离散化后的值插入到新的数组nums中
        for x,c in self.add:
            new_x = self.find(index,x)
            self.nums[new_x] += c

        # 求一遍前缀和
        for i in range(1,len(index)+1):
            self.prefix[i] = self.prefix[i-1] + self.nums[i]

        # 处理离散化后的区间和查询操作
        for l,r in self.query:
            new_l = self.find(index,l)
            new_r = self.find(index,r)
            print(self.prefix[new_r] - self.prefix[new_l-1])

if __name__ == '__main__':
    discretization = Discretization()
    discretization.main()

AcWing 803. 区间合并

给定 n 个区间 [ l i , r i ] [l_i,r_i] [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。

例如:
[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。

输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。

输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。

数据范围
1 ≤ n ≤ 100000 , 1≤n≤100000, 1n100000,
− 1 0 9 ≤ l i ≤ r i ≤ 1 0 9 −10^9≤l_i≤r_i≤10^9 109liri109

输入样例:
5
1 2
2 4
5 6
7 8
7 9

输出样例:
3


解析:

  1. 读取所有区间,对所有区间按左边界值进行从大到小排序
  2. 定义一个最小的边界值,来作为初始边界( e d = − 2 e 9 ed = -2e9 ed=2e9)
  3. 依次遍历所有区间,当当前区间的左边界大于上一个区间的右边界时,说明两个区间不存在交点,就将当前区间加入到结果列表中
  4. 特判:如果ed未被更新过,说明区间为空
class Solution:
    def main(self):
        n = int(input())
        zone = list()
        for i in range(n):
            line = list(map(int,input().split()))
            zone.append(line)

        zone.sort(key=lambda x:x[0])
        # 定义一个初始右边界
        ed = -2e9
        res = []
        for l,r in zone:
            if l > ed: 
                # 如果当前区间的左端点小于上一个区间的右边界,
                # 则说明两个区间不存在相交,将当前区间加入到结果列表中
                ed = r
                res.append([l,ed])
            else:
                # 如果两个区间相交,就取较大的右边界值取更新新的右边界
                ed = max(ed,r)
        if ed == -2e9:print(0) # 如果n=0,说明ed没有被更新过,直接返回0
        else:print(len(res))

if __name__ == '__main__':
    solution = Solution()
    solution.main()

第二章 数据结构

单链表

AcWing 826. 单链表

实现一个单链表,链表初始为空,支持三种操作:

向链表头插入一个数;删除第 k 个插入的数后面的数;在第 k 个插入的数后插入一个数。现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。

注意:
题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

输入格式
第一行包含整数 M,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x,表示向链表头插入一个数 x。
D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。

输出格式
共一行,将整个链表从头到尾输出。

数据范围
1≤M≤100000
所有操作保证合法。

输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6

输出样例:
6 4 6 5


  • 模板(C++)
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
   
    head = -1;
    idx = 0;
}

// 在链表头插入一个数a
void insert(int a)
{
   
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
   
    head = ne[head];
}
  • Python代码
class Solution:
    def __init__(self):
        N = 100010
        self.e = [0] * N
        self.ne = [0] * N
        self.head = -1
        self.idx = 1

    def add_to_head(self,x):
        """
        在头结点后面插入一个数
        head --> -1
        head --> x --> -1
        :param x:
        :return:
        """
        self.e[self.idx] = x # 先存储需要插入的值
        self.ne[self.idx] = self.head # 将当前节点的next指针指向头结点指向的节点
        self.head = self.idx # 将头结点改向
        self.idx += 1 # 将idx+1,说明当前节点已经被使用

    def add_behind_k(self,k,x):
        """
        在第k个节点后插入
        head --> x1 --> k --> -1
        head --> x1 --> k --> x --> -1
        :param k:
        :param x:
        :return:
        """
        self.e[self.idx] = x  # 先存储需要插入的节点的值
        self.ne[self.idx] = self.ne[k] # 将当前节点的next指针指向节点k指向的节点
        self.ne[k] = self.idx # 将节点k的指向改向
        self.idx += 1

    def remove_k(self,k):
        """
        head --> x1 --> k --> x2 --> x3 --> -1
        head --> x1 --> k --> x3 --> -1
        :param k:
        :return:
        """
        if k == 0:self.head = self.ne[self.head] # 如果k等于0,说明需要删除头结点
        else:self.ne[k] = self.ne[self.ne[k]] # 删除节点k后面的节点就是将节点k的next指针指向next的next

    def main(self):
        M = int(input())
        for i in range(M):
            line = input().split()
            if line[0] == "H":
                x = int(line[1])
                self.add_to_head(x)
            elif line[0] == "I":
                k,x = int(line[1]),int(line[2])
                self.add_behind_k(k,x)
            else:
                k = int(line[1])
                self.remove_k(k)
		# 遍历链表,从链表的头结点开始遍历
        idx = self.head
        while ~idx: # 如果idx等于-1,说明已经遍历到节点末尾,则跳出循环
            print(self.e[idx],end=' ')
            idx = self.ne[idx] # 逐个移动节点

if __name__ == '__main__':
    solution = Solution()
    solution.main()

双链表

AcWing 827. 双链表


实现一个双链表,双链表初始为空,支持 5 种操作:

在最左侧插入一个数;
在最右侧插入一个数;
将第 k 个插入的数删除;
在第 k 个插入的数左侧插入一个数;
在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。

注意:
题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

L x,表示在链表的最左端插入数 x。
R x,表示在链表的最右端插入数 x。
D k,表示将第 k 个插入的数删除。
IL k x,表示在第 k 个插入的数左侧插入一个数。
IR k x,表示在第 k 个插入的数右侧插入一个数。

输出格式
共一行,将整个链表从左到右输出。

数据范围
1≤M≤100000
所有操作保证合法。

输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

输出样例:
8 7 7 3 2 9

  • 模板(C++)
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;

// 初始化
void init()
{
   
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}

// 在节点a的右边插入一个数x
void insert(int a, int x)
{
   
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

// 删除节点a
void remove(int a)
{
   
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}
  • Python代码
class Solution:
    def __init__(self):
        N = 100010
        self.e = [0] * N
        self.left = [0] * N
        self.right = [0] * N
        self.left[1] = 0 # 初始化节点,节点0-->1; 0<--1;节点0表示头结点,节点1表示尾节点
        self.right[0] = 1
        self.idx = 2 # 0和1已经占用两个节点,所以idx从2开始

    def add_right_behind_k(self,k,x):
        """
        在节点k的右边插入节点
        0 --> x1 --> k --> 1
        0 <-- x1 <-- k <-- 1

        0 --> x1 --> k --> x --> 1
        0 <-- x1 <-- k <-- x <-- 1
        :param k: 在节点k后面插入节点
        :param x: 插入的节点的值
        :return:
        """
        self.e[self.idx] = x # 先将当前节点的值存储
        self.right[self.idx] = self.right[k] # 当前节点的右指针指向节点k的右节点
        self.left[self.idx] = k # 当前节点的左指针指向节点k
        self.left[self.right[k]] = self.idx # 节点k的右节点的左指针指向当前节点
        self.right[k] = self.idx #节点k的右指针指向当前节点
        self.idx 
  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值