牛客动态规划专项 - 01 线性DP

376 阅读4分钟

「这是我参与11月更文挑战的第 1 天,活动详情查看:2021最后一次更文挑战」。

  1. NC1 子数组最大连续和
  2. NC2 不相邻取数
  3. NC3 nico和niconiconi

子数组最大连续和

给定一个长度为 的数组,数组中的数为整数。

请你选择一个非空连续子数组,使该子数组所有数之和尽可能大。求这个最大值。

输入描述:

第一行为一个正整数 ,代表数组的长度。 1≤n≤10^5

第二行为 个整数 ai​,用空格隔开,代表数组中的每一个数。∣ai​∣≤10^9

输出描述:

连续子数组的最大之和。

示例1

输入:

3
3 -4 5

输出:

5

分析

求子数组的最大值, 以 i 结尾的子数组最大值有两种可能

  1. 以 i - 1 结尾子数组的最大值 + i位置的数
  2. nums[i]

定义 dp 数组, dp[i] 表示 以 i 结尾子数组的最大值, 遍历 dp 数组,找到最大值 即为所求

dp[i] = ( i - 1 >= 0 && dp[i - 1] > 0) ? dp[i - 1] + nums[i] : nums[i];

AC 代码:

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int len = in.nextInt();
           
            int[] arr = new int[len];
            
            int index = 0;
            while(index < len){
                arr[index++] = in.nextInt();
            }
            
            long ans = getResult(arr);
            System.out.println(ans);
        }
    }
    
    static long getResult(int[] arr){
        long dp = arr[0];
        long ans = arr[0];
        
        for(int i = 1 ; i < arr.length; i++){
            dp = dp > 0 ? dp + arr[i] : arr[i];
            ans = Math.max(ans, dp);
        }
        return ans;
    }
}

不相邻取数

小红拿到了一个数组。她想取一些不相邻的数,使得取出来的数之和尽可能大。你能帮帮她吗?\

输入描述:

第一行输入一个正整数  ,代表数组长度

第二行输入  个正整数 ai​ ,代表整个数组。

1≤n≤10^5 , 1≤ai​≤10^9

输出描述:

不相邻的数的最大和。

示例1

输入:

4
2 6 4 1

输出:

7

分析:

不相邻取数, 定义 dp 数组 dp[i] 表示 从以 i 结尾的子数组中不相邻取数获取的最大值

对于 dp[i], 如果 取 i 位置的数字 当前取出的数之和 为 dp[i-2] + nums[i], 如果不取 i 位置的数字 当前取出的数之和 为 dp[i-1]

dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])

AC 代码

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int len = in.nextInt();
            int[] nums = new int[len];
            for(int i = 0 ; i < len;i++){
                nums[i] = in.nextInt();
            }
            System.out.println(getResult(nums));
        }
    }
    
    
    static long getResult(int[] nums){

       long ans = nums[0];
       long pre = nums[0];
       long lastTwo = 0;

        for (int i = 1; i < nums.length; i++) {
            long rob = i - 2 >= 0 ? lastTwo + nums[i] : nums[i];
            ans = Math.max(pre, rob);
            lastTwo = pre;
            pre = ans;
        }

        return ans;

    }
}

nico和niconiconi

给定一个字符串 S ,定义三种有价值的字符串: A = "nico" ,B = "niconi" , C = "niconiconi"

其中,字符串 A 的价值为 a, 字符串 B 的价值为 b,字符串 C 的价值为 c

她拿到了一个长度为 n 的字符串,你需要在其中找到一些有价值的子串 (指字符串中连续的一段) 并统计价值之和的最大值。

注:已被计算价值过的字符不能重复计算价值!如 "niconico" 要么当作 "nico" + "nico" 计 2a 分,要么当作 "niconi" + "co" 计 b 分(其中 "co" 不计分)。

输入描述:

第一行四个正整数 。 

第二行是一个长度为  的字符串。

输出描述:

一个整数,代表最大的计数分数。

示例1

输入:

19 1 2 5
niconiconiconiconi~

输出:

7

说明:

"niconi"co"niconiconi"~

故为2+5=7

分析:

定义 dp 数组, dp[i] 表示 以位置 i 结尾的字符串的最大价值

首先 : **dp[i] = dp[i - 1] **( i >= 0)

当 i 位置的字符为 ‘o’ 时, 并且 i - 3 >= 0 && s.substring(i - 3, i + 1).equals("nico") 时 : dp[i] = Math.max(dp[i], a + (i >= 4?dp[i - 4] : 0));

依次类推 当 i 位置的字符为‘i’时的

  • i>=5 && str.substring(i-5,i+1).equals("niconi") : dp[i] = Math.max(dp[i], b + (i>=6?dp[i-6] : 0))
  • i>=9 && str.substring(i-9,i+1).equals("niconiconi") : dp[i] = Math.max(dp[i], c + (i>=10?dp[i-10] : 0))

AC 代码:

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int len = in.nextInt();
            int a = in.nextInt();
            int b = in.nextInt();
            int c = in.nextInt();
            
            String str = in.next();
            System.out.println(getScore(str, a, b, c));
        }
    }
    
    
    static long getScore(String str, int a, int b, int c){
        int len = str.length();
        
        long[] dp = new long[len];
        
        for(int i = 3; i< len;i++){
            dp[i]= dp[i-1];
            if(i>=3 && str.substring(i-3,i+1).equals("nico")){
              
                dp[i] = Math.max(dp[i], a + (i>=4?dp[i-4] : 0));
            }
            
            if(i>=5 && str.substring(i-5,i+1).equals("niconi")){
                dp[i] = Math.max(dp[i], b + (i>=6?dp[i-6] : 0));
            }
            
            if(i>=9 && str.substring(i-9,i+1).equals("niconiconi")){
                dp[i] = Math.max(dp[i], c + (i>=10?dp[i-10] : 0));
            }
        }
        
        return dp[len-1];
    }
}

TIP

线性 DP 可以考虑使用有限的变量替代 DP 叔祖, 降低空间复杂度