【无标题】

【算法练习】——淘天240427笔试

T1.前后缀和

题目描述:
小红定义一个长度为偶数的数组的权值为:每两个元素之间依次填入乘号和加号(第一个先填乘号),然后计算结果。例如,输入[1,2,3,4]的权值为1x2+3x4=14。
现在小红拿到了一个长度为奇数的数组,她定义 f ( i ) f(i) f(i)为,若删除第 i i i个元素,剩余的元素构成的数组的权值。请你帮小红求出 f ( 1 ) , f ( 2 ) , . . . , f ( n ) f(1),f(2),...,f(n) f(1),f(2),...,f(n)的值。

输入描述
第一行输入一个正整数 n n n,代表小红拿到的数组。
第二行输入 n n n个正整数 a i a_i ai,代表数组中的元素。
3 ≤ n ≤ 1 0 5 3 \leq n \leq 10^5 3n105
1 ≤ a i ≤ 1 0 5 1 \leq a_i \leq 10^5 1ai105
保证 n n n是奇数。

输出描述
输出 n n n行,第 i i i行代表 f ( i ) f(i) f(i)的值。

样例输入

5
1 2 3 4 5

样例输出

26
23
22
17
14

思路:
乍一看以为就是一个简答的模拟,结果用双重for循环严重超时。就只能思考用什么算法:
前缀和+后缀和+奇偶分情况

i=0,f=a[1]*a[2]+a[3]*a[4]
i=1,f=a[0]*a[2]+a[3]*a[4]
i=2,f=a[0]*a[1]+a[3]*a[4]
i=3,f=a[0]*a[1]+a[2]*a[4]
i=4,f=a[0]*a[1]+a[2]*a[3]

通过观察规律可以发现:
(如果把x*x+x*x叫做变相的“和”的话)
当i为偶数时,f=(i-1开始的前缀和)+(i+1开始的后缀和)
 所以需要计算下标为奇数的前缀和、后缀和
当i为奇数时,f=(i-2开始的前缀和)+(i+2开始的后缀和)+a[i-1]+a[i+1]
 所以需要计算下标为奇数的前缀和、后缀和
总结:需要计算下标为奇数的前缀和、后缀和;然后再分奇偶、分下标越界进行计算

Java实现:

public class TaoTian1 {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int[] a=new int[n];
        for(int i=0;i<n;i++){
            a[i]=in.nextInt();
        }
        //计算前缀和数组、后缀和数组
        int[] pre=new int[n];
        int[] suf=new int[n];
        int cur=0;
        for(int i=1;i<n;i+=2){
            cur+=a[i]*a[i-1];
            pre[i]=cur;
        }
        cur=0;
        for(int i=n-2;i>=0;i-=2){
            cur+=a[i]*a[i+1];
            suf[i]=cur;
        }
        //分奇偶讨论
        for(int i=0;i<n;i++){
            int res=0;
            if(i%2==0){
                if(i-1>=0){
                    res+=pre[i-1];
                }
                if(i+1<n){
                    res+=suf[i+1];
                }
            }else{
                if(i-2>0){
                    res+=pre[i-2];
                }
                if(i+2<n-1){
                    res+=suf[i+2];
                }
                res+=a[i-1]*a[i+1];
            }
            System.out.println(res);
        }
    }
}

T2.树的度数

题目描述:
​ 小红拿到了一个无向图,她准备选择两条边,满足这两条边的4个节点各不相同。小红想知道,有多少种选择边的方案?

输入描述:
第一行输入两个正整数 n n n m m m,代表无向图的节点数量和边的数量。
接下来的 m m m行,每行输入2个正整数 u , v u,v u,v,代表节点 u u u和节点 v v v有一条边连接。
1 ≤ n , m ≤ 1 0 5 1 \leq n,m \leq 10^5 1n,m105
1 ≤ u , v ≤ n 1 \leq u,v \leq n 1u,vn
保证给定的图不包含重边和自环。

输出描述:
一个整数,代表选择边的方案数。

样例输入:

4 4
1 2
2 3
3 4
4 1

样例输出:

2

说明

方案1:选择第1条边和第3条边。
方案2:选择第2条边和第4条边。

思路:
任意选择两条边的方案数: C m 2 C_m^2 Cm2
减去两条边相邻的情况。就是最终的方案数。
如何计算两条边相邻的情况数呢?
两条边相邻是因为连接在同一个节点上,也就是说,一个节点上所有的边都是相邻的。这是无向图,所以需要计算节点的度数,节点的度数为 k k k,则该节点就会导致 C k 2 C_k^2 Ck2中两条边相邻的情况。

Java实现:


public class TaoTian2 {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int m=in.nextInt();

        //记录每一个节点的度数
        long[] deg=new long[n];
        for(int i=0;i<m;i++){
            int u=in.nextInt()-1;
            int v=in.nextInt()-1;
            deg[u]++;
            deg[v]++;
        }

        long ans=(long)m*(m-1)/2;
        for(int i=0;i<n;i++){
            ans-=deg[i]*(deg[i]-1)/2;
        }
        System.out.println(ans);
    }
}

T3.回溯

题目描述:
小红拿到了一个长度为 n n n的数组。她定义一个子序列是“好的”,当且仅当该子序列所有元素之和为奇数。
现在小红想求出所有“好的”子序列的元素和之和。你能帮帮她吗? 由于答案可能过大,请对 1 0 9 + 7 10^9+7 109+7取模。
定义一个数组的子序列是,数组中取若干元素 (可以不连续) 按原数组顺序形成的新数组。

输入描述:
第一行输入一个正整数 n n n,代表小红拿到的数组。
第二行输入 n n n个正整数 a i a_i ai,代表数组中的元素。
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105
1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109

输出描述:
所有“好的”子序列的元素和之和,答案对 1 0 9 + 7 10^9+7 109+7取余。

样例输入:

3
1 3 2

样例输出:

12

说明:

好的子序列有以下几个:
[1],[3],[1,2],[3,2],答案是1+3+3+5=12

思路:
用回溯算法进行暴力搜索,但是会超时!目前我不知道还可以怎样做,求各位大佬指教!
Java实现:(回溯)

public class TaoTian3 {
    public static List<Integer> path;
    public static long res;
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n=in.nextInt();
        int[] arr=new int[n];
        for(int i=0;i<n;i++){
            arr[i]=in.nextInt();
        }
        path=new ArrayList<>();
        res=0;
        long sum=0;
        tranverse(arr,0,sum);
        System.out.println((int)(res%(int)(1e9+7)));
    }
    public static void tranverse(int[] arr,int start,long sum){
        if(sum%2!=0){
            res+=sum;
        }
        if(start>=arr.length){
            return;
        }
        for(int i=start;i<arr.length;i++){
            path.add(arr[i]);
            sum+=arr[i];
            tranverse(arr,i+1,sum);
            path.remove(path.size()-1);
            sum-=arr[i];
        }
    }
}

T3番外.动态规划

和T3类似,但是可以用动态规划解决:
力扣1524.和为奇数的子数组数目
给你一个整数数组 arr 。请你返回和为 奇数 的子数组数目。
由于答案可能会很大,请你将结果对 1 0 9 + 7 10^9+7 109+7 取余后返回。

示例 1:
输入:

arr = [1,3,5]

输出:

4

解释:

所有的子数组为 [[1],[1,3],[1,3,5],[3],[3,5],[5]] 。
所有子数组的和为 [1,4,9,3,8,5]。
奇数和包括 [1,9,3,5] ,所以答案为 4 。

示例 2 :
输入:

arr = [2,4,6]

输出:

0

解释:

所有子数组为 [[2],[2,4],[2,4,6],[4],[4,6],[6]] 。
所有子数组和为 [2,6,12,4,10,6] 。
所有子数组和都是偶数,所以答案为 0 。

示例 3:
输入:

arr = [1,2,3,4,5,6,7]

输出:

16

思路:
首先定义状态:
dp[i][0]表示以arr[i]结尾的子数组和为奇数的个数;
dp[i][1]表示以arr[i]结尾的子数组和为偶数的个数。
接着是状态转移方程:
我们知道奇数+偶数=奇数;奇数+奇数=偶数,所以当当前元素arr[i]是奇数时:
dp[i][0]=dp[i-1][1]+1;//+1是算上他自己
dp[i][1]=dp[i-1][0];
偶数+奇数=奇数;偶数+偶数=偶数,所以当前元素arr[i]是偶数时:
dp[i][0]=dp[i-1][0];
dp[i][1]=dp[i-1][1]+1;//+1是算上他自己

最后的res就是dp[i][0]之和
Java实现:

class Solution {
    public int numOfSubarrays(int[] arr) {
        int n=arr.length;
        int[][] dp=new int[n][2];
        long res=0l;
        if(arr[0]%2!=0){//首元素为奇数
            dp[0][0]=1;
            dp[0][1]=0;
            res++;
        }else{
            dp[0][0]=0;
            dp[0][1]=1;
        }
        for(int i=1;i<n;i++){
            if(arr[i]%2!=0){//arr[i]为奇数
                dp[i][0]=dp[i-1][1]+1;
                dp[i][1]=dp[i-1][0];
            }else{
                dp[i][0]=dp[i-1][0];
                dp[i][1]=dp[i-1][1]+1;
            }
            res+=dp[i][0];
        }
        res%=(int)(1e9+7);
        return (int)res;
    }
}

参考:
https://www.xiaohongshu.com/user/profile/63e06f7a00000000260076f8

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值