元素互不相邻的最大和子数组

题目

对于一个给定的数组,在其中选取其子数组,要求相邻的元素不能选取,且要保证选出的子数组元素和最大。输入数组长度及其元素,输出所选子数组的和。

line

  • 测试输入
    7
    4 2 6 1 3 5 8
  • 测试输出
    21

分析1

为了让子数组和最大,应该尽可能让它包含更多的元素,并且相邻元素不能选取,所以选取的任意两个数字之间最多间隔两个数,因为假设如果间隔了三个而子数组和最大,那么最中间的那个数一定可以选中,此时子数组和也一定比之前更大,产生矛盾。由此可见,本题只需要分析连续的三个元素的关系即可。
按照第 i i i个元素是否被选取,前 i i i个元素的和要么与前 i − 1 i-1 i1个元素的和相同(不选取),要么是前 i − 2 i-2 i2个元素的和加上此第 i i i个元素(选取),这两种情况取最大。这很容易通过递归实现出来,也可以使用动态规划实现。要用动态规划,子问题的选取需要具有无后效性,即前 i i i个元素的选取只能和之前的选取有关,和未来的情况无关。对于数组 a r r a y [ i ] array[i] array[i] i = 0 ∼ n − 1 i=0\sim n-1 i=0n1,定义 s [ i ] s[i] s[i]表示前 i i i个元素的最大和,则递归式为

s [ i ] = { 0 , i = 0 a r r a y [ 1 ] , i = 1 max ( s [ i − 1 ] , s [ i − 2 ] + a r r a y [ i ] ) , 2 ≤ i ≤ n s[i]=\left\{\begin{array}{ll} 0, & i=0\\ array[1], & i=1\\ \textrm{max}(s[i-1], s[i-2]+array[i]), & 2\le i\le n \end{array}\right. s[i]=0,array[1],max(s[i1],s[i2]+array[i]),i=0i=12in

代码1

import java.util.Scanner;

public class NotAdjacentLine {
    static int solution(int[] array, int n) {
        if (n < 1) return 0;
        if (n == 1) return array[0];
        return Math.max(solution(array, n - 1), 
				        solution(array, n - 2) + array[n - 1]);
    }

    static int solution2(int[] array, int n) {
        int[] s = new int[n + 1];
        s[0] = 0;
        s[1] = array[0];
        for (int i = 2; i < n + 1; i++) {
            int takei = s[i - 2] + array[i - 1];
            int skipi = s[i - 1];
            s[i] = Math.max(takei, skipi);
        }
        return s[n];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = sc.nextInt();
        }
        System.out.println(solution2(array, n));
    }
}

变形

将上述的数组变成一个环,其他条件保持不变。

circle

  • 测试输入
    7
    4 2 6 1 3 5 8
  • 测试输出
    17

分析2

对于环的情况,仍然可以利用上述直线的方法完成,只是有一些变化。首先要保证首尾元素不能同时选取,它们也属于相邻元素,这可以通过构造最优解来判断:如果首尾元素同时选取,则删除尾元素而保留首元素;其次为了找到所有可取的情况,需要对数组不断进行循环移位,从每一个元素开始,找出能够取得的最大和。这里对数组的循环移位实际上只需要做两次即可,因为我们求解 s [ i ] s[i] s[i]时,只与 s [ i − 1 ] s[i-1] s[i1] s [ i − 2 ] s[i-2] s[i2]有关,环上的每一个元素位置等价,即都要能够成为这三项中的任意一项,这里位置不等价的只有四个元素:首位元素不能成为 s [ i − 1 ] s[i-1] s[i1] s [ i ] s[i] s[i],第二元素不能成为 s [ i ] s[i] s[i],而倒数第二元素不能成为 s [ i − 2 ] s[i-2] s[i2],末尾元素不能成为 s [ i − 2 ] s[i-2] s[i2] s [ i − 1 ] s[i-1] s[i1]。所以为了满足环上元素位置等价的条件,只需要再循环移动数组两次即可,最后比较这三种情况取最大值。

代码2

import java.util.Scanner;

public class NotAdjacentCircle {
    static int solution(int[] array, int n) {
        int[] s = new int[n + 1];
        boolean[] isUsed = new boolean[n];// 元素是否使用,用于构造最优解
        s[0] = 0;
        s[1] = array[0];
        isUsed[0] = true;
        for (int i = 2; i < n + 1; i++) {
            int takei = s[i - 2] + array[i - 1];
            int skipi = s[i - 1];
            if (takei > skipi) {
                s[i] = takei;
                isUsed[i - 1] = true;
            } else {
                s[i] = skipi;
                isUsed[i - 1] = false;
            }
        }
        return makeCircle(s, isUsed, n);
    }

    static int makeCircle(int[] s, boolean[] isUsed, int n) {
        if (!isUsed[n - 1]) return s[n];// 最后一个元素没用,首尾不会相邻
        int i = n - 1;
        boolean isFirstUsed = false;// 第一个元素是否使用
        while (i >= 0) {// 构造最优解的过程
            if (isUsed[i]) {
                if (i == 0) isFirstUsed = true;
                i -= 2;
            } else {
                i -= 1;
            }
        }
        return isFirstUsed ? s[n - 1] : s[n];// 如果首尾相邻,则删除尾元素
    }

    static int[] leftShift(int[] array, int steps) {
        int n = array.length;
        int[] newArray = new int[n];
        System.arraycopy(array, steps, newArray, 0, n - steps);
        System.arraycopy(array, 0, newArray, n - steps, steps);
        return newArray;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = sc.nextInt();
        }
        int r1 = circleSolution(array, n);
        array = leftShift(array, 1);
        int r2 = circleSolution(array, n);
        r2 = Math.max(r1, r2);
        array = leftShift(array, 1);
        int r3 = circleSolution(array, n);
        r3 = Math.max(r2, r3);
        System.out.println(r3);
    }
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值