记录一些笔试题及题解(线性dp算法题)

简单难度

1.收集大写字母

描述

小明是一个喜欢拼图的小男孩,有一天,他收到了一份神秘的礼物。打开后,他发现里面是一个由英文字母组成的二维矩阵,每个字母都是一块拼图。小明哥觉得很好奇,就开始拼接这个矩阵。他发现,这个矩阵有一个特殊的规则,就是他只能从左上角开始,每次向右或向下移动一步,然后收集拼图上的英文字母。而且,每个字母都有一个分数,大写字母的分数比小写字母的分数高,所以小明很想收集大写字母。小明想知道,他最多可以收集多少个大写字母。你能帮帮他吗?

输入输出

输入: 第一行输入两个正整数n和m ,代表矩阵的行数和列数。接下来的n行每行输入一个长度为m的仅包含大写和小写字母的字符串。
输出: 输出 一个整数,代表小明最多可以收集到的大写字母。
示例:
输入:
5 5
AaBbb
bAaBa
CacAc
AbcBC
cbAbA
输出:
7

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //收集大写字母
        int m=in.nextInt();
        int n=in.nextInt();
        char[][] letters=new char[m][n];
        for (int i=0;i<m;++i){
            char[] chars = in.next().toCharArray();
            for (int j = 0; j < chars.length; j++) {
                letters[i][j]=chars[j];
            }
        }
        Collectuppercaseletters collectuppercaseletters = new Collectuppercaseletters();
        System.out.println(collectuppercaseletters.getUpperLetter(letters));
    }
}
public class Collectuppercaseletters {
    public int getUpperLetter(char[][] letters){
        int m=letters.length,n=letters[0].length;
        //dp[i][j]表示从左上到当前位置的大写字母数量
        int[][] dp=new int[m][n];
        if(isUpperLetter(letters[0][0])){
            dp[0][0]=1;
        }
        for (int i = 1; i < n; i++) {
            int temp=isUpperLetter(letters[i][0])?1:0;
            dp[i][0]=dp[i-1][0]+temp;
        }
        for (int j = 1; j < m; j++) {
            int temp=isUpperLetter(letters[0][j])?1:0;
            dp[0][j]=dp[0][j-1]+temp;
        }
        //判断当前值是否是大写,若为大写则加1否则加0
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                int temp=isUpperLetter(letters[i][j])?1:0;
                dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+temp;
            }
        }
        return dp[m-1][n-1];
    }
    public boolean isUpperLetter(char ch){
        if(ch>='A'&&ch<='Z'){
            return true;
        }
        return false;
    }
}

2.机器采草莓最优决策

描述

采草莓机器人在一个n*m的草莓矩阵内,从起点坐标(0,0)出发,可以向右或向下两个方向移动,每个方格种植着不同价值的草莓,现在规定了一个阈值g,代表该机器人此行采集草莓的总价值的最低目标值,小明想知道机器人计算完成任务所需要移动的最少移动次数可以满足这个阈值?

输入输出

输入: 第一行输入三个整数:n,m,g以空格隔开,接下来有n行,每行有m个整数,以空格隔开。这n*m个数字构成草莓矩阵
输出: 机器人完成任务所需移动的最少次数,不存在则返回-1
示例:
输入:
2 2 3
1 1
1 1
输出:
2

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //机器采草莓最优决策
        int m=in.nextInt();
        int n=in.nextInt();
        int d=in.nextInt();
        int[][] matrix=new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                matrix[i][j]=in.nextInt();
            }
        }
        Machinepickingstrawberries machinepickingstrawberries = new Machinepickingstrawberries();
        System.out.println(machinepickingstrawberries.getMinNum(matrix, d));
    }
}

public class Machinepickingstrawberries {
    public int getMinNum(int[][] matrix,int g){
        int m=matrix.length,n=matrix[0].length;
        //表示从左上角到当前位置的最大值
        int[][] dp=new int[m][n];
        dp[0][0]=matrix[0][0];
        //记录最小的步数
        int res=Integer.MAX_VALUE;
        for (int i = 1; i < m; i++) {
            dp[i][0]=dp[i-1][0]+matrix[i][0];
            if(dp[i][0] >= g) res=Math.min(res,i);
        }
        for (int j = 1; j < n; j++) {
            dp[0][j]=dp[0][j-1]+matrix[0][j];
            if(dp[0][j] >= g) res=Math.min(res,j);
        }
        int ans=0;
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j])+matrix[i][j];
                //当超过阈值时则直接返回当前的步数不需要进行之后的遍历
                if(dp[i][j] >= g) return Math.min(res,i+j);;
            }
        }
        return res==Integer.MAX_VALUE?-1:res;
    }
}

3.跳跳棋

描述

传统的跳跳棋棋盘呈直线状,共有 N 格,起始位置和结束位置都在棋盘之外。每一格都对应一个不同的积分值,而每次跳跃都必须跳过一个或多个格子,而且不允许跳到相邻的格子上,也不可以回跳,游戏的目标是通过跳跃来获取最高的积分。

输入输出

输入: 第一行为整数N ,表示跳棋格数,第二行为每一格代表的分数M
输出: 能获得的最高积分
示例:
输入:
3
1 5 2

输出:
5

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //跳跳棋
        int n=in.nextInt();
        int[] chess=new int[n];
        for (int i = 0; i < n; i++) {
            chess[i]=in.nextInt();
        }
        Jumpchess jumpchess = new Jumpchess();
        System.out.println(jumpchess.getMaxScore(chess));
    }
}
public class Jumpchess {
    public int getMaxScore(int[] chess){
        int n=chess.length;
        //数组表示当前位置累计的最高积分为
        int[]dp=new int[n];
        dp[0]=chess[0];
        dp[1]=chess[1];
        //跟打家劫舍一致,当前的积分取决于上一个位置和上两个位置
        for (int i = 2; i < n; i++) {
            dp[i]=Math.max(dp[i-2]+chess[i],dp[i-1]);
        }
        return dp[n-1];
    }
}

4.加油

描述

小明最近决定去参加一个国际CS:GO的比赛,比赛地点在距离他家很远的一个城市。小明不想坐飞机或者火车,他觉得那样太无趣了,他想要一边开车一边享受沿途的风光。于是,他计划了一条自驾游的路线,从他家出发,经过n个加油站最终到达比赛地点。但是过程中需要加油,跑车的油箱容量有限,最多只能走2个加油站的距离就必须加油。小明不想每次都在每个加油站都停下来加油,那样太浪费时间了,他想要尽可能少地加油,但是也不能让自己的车在路上没油。所以,他想知道,在保证不会没油的情况下,他到达目的地有多少种不同的加油方案。

输入输出

输入: 输入一个整数n表示沿途加油站个数。
输出: 输出为一个整数,表示可达到目的地的方案总数。不满足条件的返回0
示例:
输入:
3
输出:
3

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //加油
        int n = in.nextInt();
        Refuel refuel = new Refuel();
        System.out.println(refuel.getProposalsNumber(n));
    }
}

public class Refuel {
    public int getProposalsNumber(int n){
        if (n<0){
            return 0;
        }
        //dp[i]表示到该加油站的方案数
        int[] dp=new int[n+1];
        if (n<=1){
            return 1;
        }
        dp[0]=1;
        dp[1]=1;
        //和爬楼梯的方案数思路一致
        for (int i = 2; i <=n; i++) {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

5.最长k阶完美子序列

描述

给定一个只含小写字母的字符串s,k阶完美子序列的定义是:t是字符串s的一个子序列。并且t中中任意一个相邻的字符在字符表中距离相差不过k,现在需要求出最长的k阶完美子序列

输入输出

输入: 每行输入一个字符串s和一个整数k
输出: 输出整数n,代表字符串s的最长k阶完美子序列长度
示例:
输入:
bcda 1
输出:
3

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //最长k阶完美子序列
        String s=in.next();
        int k=in.nextInt();
        Perfectsubsequence perfectsubsequence = new Perfectsubsequence();
        System.out.println(perfectsubsequence.getMaxSeqLen(s, k));
    }
}
public class Perfectsubsequence {
    public int getMaxSeqLen(String s,int k){
        //dp表示字符串前i个字母且以字符j为结尾时能取到的最长子序列
        int[][] dp=new int[s.length()+1][26];
        int ans=0;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < 26; j++) {
                dp[i][j]=dp[i-1][j];
            }
            //取到当前字符与'a'的距离
            int temp=s.charAt(i-1)-'a';
            for (int j = 0; j < 26; j++) {
                //如果新的字符与字符串末尾的距离未超出k
                if (Math.abs(j-temp)<=k){
                    //则将新字符加入到末尾
                    dp[i][temp]=Math.max(dp[i][temp],dp[i-1][j]+1);
                }
            }
            ans=Math.max(ans,dp[i][temp]);
        }
        return ans;
    }
}

6.最佳爬楼方案

描述

有个神奇楼梯,每次可以爬不超过k个楼梯,而每次爬楼花费的体力为max(0,目标楼梯高度-当前楼梯高度);现在想知道求从第一个楼梯到最后一个楼梯的最小花费是多少?

输入输出

输入: 第一行输入n,k.代表楼梯的阶数和最多能传送跨过多少个楼梯。第二行n个数,代表每座楼梯的高度
输出: 输出第一个楼梯到最后一个楼梯的最小花费
示例:
输入:
7 4
12 38 14 71 31 61 33
输出:
21

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
         //最佳爬楼方案
        int n=in.nextInt();
        int k=in.nextInt();
        int[] height = new int[n];
        for (int i = 0; i < n; i++) {
            height[i]=in.nextInt();
        }
        Bestclimbing bestclimbing = new Bestclimbing();
      System.out.println(bestclimbing.getMinEnerge(n,k,height));
    }
}
public class Bestclimbing {
    public int getMinEnerge(int n,int k,int[] height){
        //dp表示到第i个楼梯需要的最小花费
        int[] dp=new int[n];
        Arrays.fill(dp,Integer.MAX_VALUE);
        dp[0]=0;
        for (int i = 1; i < n; i++) {
            //从当前楼梯往前K个进行遍历,得到每个i到j的花费,取得其最小值
            for (int j = 1; j <=k&&i-j>=0; j++) {
                dp[i]=Math.min(dp[i],dp[i-j]+Math.max(0,height[i]-height[i-j]));
            }
        }
        return dp[n-1];
    }
}

中等难度

1.D路通信

描述

每次操作可以删除一个数arr_i,同时删除所有等于arr_i+1或arr_i-1的数。操作后被删除的数无法再被选中。每次操作可以获得被删除数的分数,现在他想知道通过多次操作,他最多能够获得多少分。请问最优的操作策略是什么?

输入输出

输入: 输入第一行一个正整数n,表示数组arr的长度,接下来行包含n个正整数分别为数组arr中的n个数
输出: 输出一个数字表示有效信息的长度。
示例:
输入:
tttazitazittz
tazittttt
输出:
6

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //D路通信
        String s1=in.next();
        String s2=in.next();
        Dchannelcommunication dchannelcommunication = new Dchannelcommunication();
        System.out.println(dchannelcommunication.getMaxCommonStr(s1, s2));
    }
}
public class Dchannelcommunication {
    //实质是求两个字符串的最长公共子串
    public int getMaxCommonStr(String s1,String s2){
        int n=s1.length(),m=s2.length();
        //表示s1以i为结尾和s2以j为结尾时的最长公共子串长度
        int[][] dp=new int[n+1][m+1];
        int res=0;
        //由于时公共子串需要连续
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <=m ; j++) {
                //当两个字符相等时长度+1,否则长度得重新计算
                if (s1.charAt(i-1)==s2.charAt(j-1)){
                    dp[i][j]=dp[i-1][j-1]+1;
                }else dp[i][j]=0;
                res=Math.max(dp[i][j],res);
            }
        }
        return res;
    }
}

2.删除游戏

描述

一个D路通信有如下几个性质:
高斯噪声性: 如果发出一段字符串作为消息,消息的开始前和结束后可能会出现随机高斯噪声;
内容完整性: 该过程不会丢失任何字符,字符顺序也不会发生变化;
字符统一性: 所有的消息内容和噪声都是小写字符;
现有一个消除高斯噪声的算法:
同时采用两条含有随机噪声的链路发出一段消息。
在接收侧,在接收到的两条消息当中寻找最长的那段连续公共子串,就是有效信息。
想求有效消息的长度,注意有效消息不一定是唯一的,也有可能为空, 只要求返回消息的长度

输入输出

输入: 输入两行分别代表两个字符串,分别为两条链路收到的信息,仅包含小写字母。
输出: 输出可以获得的最高分
示例:
输入:
5
1 2 3 4 5
输出:
9

代码

public class Main {
    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();
        }
        Deletegame deletegame = new Deletegame();
        System.out.println(deletegame.getMaxScore(arr));
    }
}
public class Deletegame {
    public int getMaxScore(int[] arr){
        int N=100002,n=arr.length;
        //dp[0][i]表示只考虑不大于i的并且不删除等于i的数时得到的最高得分
        //dp[1][i]表示只考虑不大于i的并且操作了所有等于i的数时得到的最高得分
        int[][]dp=new int[2][N];
        int[] cnt=new int[N];
        //统计arr数组中相同的数字数量
        for (int i = 0; i <n ; i++) {
            cnt[arr[i]]++;
        }
        //因为操作i就要删除所有等于i-1的数
        for (int i = 1; i <=100001; i++) {
            //不删除i时dp[0][i]则为:不操作i-1和操作i-1的最大值
            dp[0][i]=Math.max(dp[0][i-1],dp[1][i-1]);
            dp[1][i]=dp[0][i-1]+i*cnt[i];
        }
        return Math.max(dp[0][100001],dp[1][100001]);
    }
}

3.生灵涂炭术

描述

已知地图上有n个排成一列的地域,每个地域的能量都不一样,可以用一个数字来代表某个地域中正负能量的总数,正数代表正能量比负能量多,反之亦然。现在有两次机会可以对任何一个的区域使用生灵涂炭术,使用之后,无论该区域中能量有多少,都会清0。想知道在使用了技能以后能使得天地间的正能量最多为多少?(如果天地间都是正能量,不使用技能也是可以的)

输入输出

输入: 输入第一行仅包含一个正整数n(1<=n<=100000)表示地域数量。输入第二行包含n个整数,每个整数代表一个地域的能量总和,保证这个数值的绝对值不大于100000
**输出:**输出仅包含一个整数,即正能量最多为多少。
示例:
输入:
5
5 -10 6 1 -5
输出:
12

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //生灵涂炭术
        int N=in.nextInt();
        long[]arr=new long[N];
        for (int i = 0; i <N ; i++) {
            arr[i]=in.nextLong();
        }
        Prohibitiveabracadabra prohibitiveabracadabra = new Prohibitiveabracadabra();
        System.out.println(prohibitiveabracadabra.getMaxEnerge(arr));
    }
}
public class Prohibitiveabracadabra {
    public long getMaxEnerge(long[] energe){
        int n=energe.length;
        //dp[i][j]代表到达第i个数已经删除了j次的最大值
        long[][] dp = new long[n ][3];
        for (int i = 0; i < n ; i++) {
            Arrays.fill(dp[i], 0);
        }
        //状态初始化
        dp[0][0]=energe[0];
        dp[0][1]=0;
        dp[0][2]=0;
        for (int i = 1; i < n; i++) {
            //dp[i][2]表示到达第i个数时删除了2次的最大值
            //到达第i-1个数时删除了2次的最大值和到达第i-1个数时删除了1次的最大值可以决定其状态
            dp[i][2]=Math.max(dp[i-1][2]+energe[i],dp[i-1][1]);
            dp[i][1]=Math.max(dp[i-1][1]+energe[i],dp[i-1][0]);
            dp[i][0]=dp[i-1][0]+energe[i];

        }
        long res=Math.max(dp[n-1][0],Math.max(dp[n-1][1],dp[n-1][2]));
        return res;
    }
}

4.爬山

描述

小明是一个热爱户外运动的人,他周末经常约朋友一起登山。华光林山清水秀,景色宜人,让他感到非常愉悦。他喜欢在登山的过程中欣赏美景,感受大自然的魅力。同时,他也喜欢挑战自己,尝试攀登更高的山峰。小明登山时每一步可以选择向上爬一个台阶或者多个台阶,如果登山时选择的台阶不同,则为一种爬山方案。小明想知道,华光林的每座山各有多少种不同的爬山方案(输出结果对10^9+7 取模)

输入输出

输入: 输入第一行,三个整数N、P、K分别代表山的个数N,小明一次最高能爬的高度P以及小明一次最多能跨越的台阶数K,接下来N行每行的第一个整数M_i表示第i座山一共有M_i个台阶,接下来有M_i个整数,分别表示每个台阶的高度H_j
**输出:**输出N行每行一个整数,表示第i 座山塔子哥可以选择的登山方案数目。
示例:
输入:
3 3 2
4 1 1 1 1
4 2 2 2 2
5 2 2 2 3 4
输出:
5
1
0

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        //爬山
        int n=in.nextInt();
        int p=in.nextInt();
        int k=in.nextInt();
        for (int i = 0; i < n; i++) {
            int len=in.nextInt();
            int[] arr=new int[len];
            for (int j = 0; j < len; j++) {
                arr[j]=in.nextInt();
            }

            ClimbingMountain climbingMountain = new ClimbingMountain();
            System.out.println(climbingMountain.getMountaineeringPlan(arr, k, p));
        }
    }
}
public class ClimbingMountain {
    public int getMountaineeringPlan(int[] arr,int k,int p){
        int MOD = 1000000007;
        int n=arr.length;
        //从山底走到第i层的所有可能方案
        int[] dp=new int[n+1];
        dp[0]=1;
        for (int i = 0; i <=n; i++) {
            //统计台阶累计和
            int sum=0;
            for (int j = i+1; j <=i+k &&j<=n ; j++) {
                sum+=arr[j-1];
                //如果台阶累计和大于能爬的高度p则会跳出循环
                if (sum>p) break;
                //否则到j层的可能方案则为到i可能的方案之和
                dp[j]+=dp[i];
                dp[j]=dp[j]%MOD;
            }
        }
        return dp[n];
    }
}

5.玩游戏

描述

游戏世界有n个关卡和m种宝石。每一个关卡都有一个boss或商店。当他打败一个boss时,他可以获得一个新的宝石。但是,由于他只能携带一个宝石,他必须丢掉当前的宝石才能取走新的宝石。此外,当他到达一个商店时,商店会收购一种特定的宝石,并给他一些钱。小明想知道自己最多可以获得多少钱。因此,他开始了他的游戏之旅,希望能在这个游戏世界中获得尽可能多的财富。

输入输出

输入: 输入第一行为两个整数n和m。接下来n行,每一行第一个输入为一个字符,如果字符为b则代表boss,后面输入一个整数,代表宝石种类。如果字符为m,则代表商店,后面输入两个整数,代表该商店收取的宝石种类和价格。
**输出:**输出最多可以获得多少钱。
示例:
输入:
5 3
b 3
b 1
m 3 50
b 2
m 1 100
输出:
5
1
0

代码

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
       //玩游戏
        //关卡数
        int n=in.nextInt();
        //宝石种类
        int m=in.nextInt();
        // dp[i] 为第1~i个关卡被探索后选择宝石能达到的最大值
        int[] dp=new int[n+1];
        //记录宝石交易路线的最大收益
        int[] presum=new int[m+1];
        Arrays.fill(presum, Integer.MIN_VALUE);
        int max=Integer.MIN_VALUE;
        in.nextLine();
        for (int i=1;i<=n;++i){
            String[] s = in.nextLine().split(" ");
            //如果该关为商店
            if (s[0].equals("m")){
                //宝石类型
                Integer t=Integer.valueOf(s[1]);
                //宝石价格
                Integer v=Integer.valueOf(s[2]);
                //可以选择不交易宝石和交易宝石两种可能的情况
                dp[i]=Math.max(dp[i-1],presum[t]+v);

            }else {
                //如果是打boss
                Integer v=Integer.valueOf(s[1]);
                // 当前打败了boss,获得宝石的选择权
                // 这样写可以联系第一个样例来看,遇到商店一定出售的做法不一定最优,可以有如下选择
                // (1)选择不出售宝石,也就是当前没有拿可以出售的那种宝石,选择从上一次此宝石的价值转移
                // (2)选择出售宝石,也就是将这个宝石带上,到商店再看,可能卖,可能不卖
                presum[v]=Math.max(presum[v],dp[i-1]);
                // 这里需要对上一次交易的最优解进行继承,因为这里打了boss,但是先前的总最大收益不变
                dp[i] = dp[i - 1];
            }
            max=Math.max(max,dp[i]);
        }
        System.out.println(max);
    }
}
  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值