Offer II 102+LC667+1306+2321

加减的目标值

给定一个正整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

分析:

首先想到用回溯,列出所有情况找出结果为target的个数,但时间效率较低。

class Solution {
    int count = 0;
    public int findTargetSumWays(int[] nums, int target) {
        backtrack(nums, 0, target , 0);
        return count;
    }
    public void backtrack(int[] nums, int index, int target, int sum){
        if (index == nums.length){
            if (sum == target) count++;
            return;
        }
        backtrack(nums, index+1, target, sum+nums[index]);
        backtrack(nums, index+1, target, sum-nums[index]);
    }
}

第二种方法是动态规划。设数组中取正号的数字之和为sum1,取符号的数字之和为sum2,数组值和为sum,则有sum1-sum2=(sum-sum2)-sum2=target,即 s u m 2 = s u m − t a r g e t 2 sum2 = \frac{sum-target}{2} sum2=2sumtarget。由于数组nums 中的元素都是非负整数,sum2 也必须是非负整数,所以上式成立的前提是sum−target 是非负偶数。若不符合该条件可直接返回 0。
于是定义二维数组dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示nums[0:i]中取若干数字之和能否等于j。初始化: d p [ 0 ] [ 0 ] = 1 , d p [ 0 ] [ j ] = 0 , 1 < = j < = s u m − t a r g e t 2 dp[0][0]=1,dp[0][j]=0,1<=j<=\frac{sum-target}{2} dp[0][0]=1,dp[0][j]=01<=j<=2sumtarget。开始遍历 1 < = i < = n , 1 < = j < = s u m − t a r g e t 2 1<=i<=n,1<=j<=\frac{sum-target}{2} 1<=i<=n,1<=j<=2sumtarget, 令num=nums[i]:

  • 如果j<num, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j];
  • 如果j>=num, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m ] dp[i][j]=dp[i-1][j]+dp[i-1][j-num] dp[i][j]=dp[i1][j]+dp[i1][jnum]
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        int diff = sum - target;
        if (diff < 0 || diff % 2 != 0) {
            return 0;
        }
        int n = nums.length, neg = diff / 2;
        int[][] dp = new int[n + 1][neg + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int num = nums[i - 1];
            for (int j = 0; j <= neg; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= num) {
                    dp[i][j] += dp[i - 1][j - num];
                }
            }
        }
        return dp[n][neg];
    }
}

优美的排列 II

给你两个整数 n 和 k ,请你构造一个答案列表 answer ,该列表应当包含从 1 到 n 的 n 个不同正整数,并同时满足下述条件:

  • 假设该列表是 answer = [a1, a2, a3, … , an] ,那么列表 [|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] 中应该有且仅有 k 个不同整数。

返回列表 answer 。如果存在多种答案,只需返回其中 任意一种 。

分析:

想要找到有k个不同整数的序列,设列表的数字最大为n,则k的取值范围是[1,n-1]。构造任意k的序列,可以将序列分为两部分:
在构造前边的摆动序列时,先摆放前k个数字,表示不同的k-1个整数,然后摆放再后边|ak-1 - ak| = 1的序列。前k个数字采用一大一小排列,后边的序列采用:

  • 当k等于偶数时,后边|ak-1 - ak| = 1的序列一定是降序序列。
  • 当k等于奇数时,后边|ak-1 - ak| = 1的序列一定是升序序列。
class Solution {
    public int[] constructArray(int n, int k) {
        int[] ans = new int[n];
        int i = 0;
        //p从小到大 q从大到小
        int p = 1, q = n;
        //构造前k个数组 k-1个不同的整数
        //奇数位从大到小,偶数位从小到大
        for (int j = 0; j < k; j++) {
            if (j % 2 == 0) {
                ans[i++] = p++;
            } else {
                ans[i++] = q--;
            }
        }
        //构造剩下的绝对值为1的序列
        if (k % 2 == 0) {
            //偶数时,降序
            while (i < n) {
                ans[i++] = q--;
            }
        } else {
            //奇数时,升序
            while (i < n) {
                ans[i++] = p++;
            }
        }
        return ans;
    }
}

跳跃游戏 III

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

分析:

采用dfs。首先定义一个visited数组来记录当前访问的节点有没有被访问过,当前搜索的arr[start]==0,则返回true,如果start<0或者start>=arr.length或者visited[start]=true则直接返回false。如果以上情况都不是则递归去访问start+arr[start]和start-arr[start]。

class Solution {
    public boolean canReach(int[] arr, int start) {
        boolean[] visited = new boolean[arr.length];
        return dfs(arr,start,visited);
    }
    boolean dfs(int[] arr, int start, boolean[] v){
        if (start>=arr.length || start<0) return false;
        if (v[start]) return false;
        v[start] = true;
        if (arr[start] == 0) return true;
        return dfs(arr, start+arr[start],v) || dfs(arr, start-arr[start],v);
    }
}

拼接数组的最大分数

给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,长度都是 n 。

你可以选择两个整数 left 和 right ,其中 0 <= left <= right < n ,接着 交换 两个子数组 nums1[left…right] 和 nums2[left…right] 。

  • 例如,设 nums1 = [1,2,3,4,5] 和 nums2 = [11,12,13,14,15] ,整数选择 left = 1 和 right = 2,那么 nums1 会变为 [1,12,13,4,5] 而 nums2 会变为 [11,2,3,14,15] 。

你可以选择执行上述操作 一次 或不执行任何操作。

数组的 分数 取 sum(nums1) 和 sum(nums2) 中的最大值,其中 sum(arr) 是数组 arr 中所有元素之和。

返回 可能的最大分数 。

子数组 是数组中连续的一个元素序列。arr[left…right] 表示子数组包含 nums 中下标 left 和 right 之间的元素(含 下标 left 和 right 对应元素)。

分析:

令s1=sum(nums1),则交换之后的和s = s1-(nums1[left]+…+nums1[right])+(num2[left]+…+nums2[right]),整理得s=s1+(nums2[i]-nums1[i]),left<=i<=right。令differ[i]=nums2[i]-nums1[i],只需求出最大的differ[left]+…+differ[right]即可。

class Solution {
    public int maximumsSplicedArray(int[] nums1, int[] nums2) {
        return Math.max(helper(nums1, nums2),helper(nums2, nums1));
    }
    public int helper(int[] nums1, int[] nums2){
        int maxAdd = 0,sum = 0;
        for (int i = 0, add = 0; i < nums1.length; i++) {
            sum += nums1[i];
            add = Math.max(add+nums2[i]-nums1[i], 0);
            maxAdd = Math.max(maxAdd, add);
        }
        return sum + maxAdd;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值