欢迎指教 欢迎评论留言
给定一个整数数组
nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
public int maxSubArray(int[] nums) {
// 最优子串可能在左串 可能在右串 可能是包含中间元素的中间串
return recur(nums,0,nums.length-1);
}
// 递归 分治法
private int recur(int[] nums, int l, int r) {
if(l==r) return nums[l];// 只有一个元素的时候
int mid=(l+r)/2;
// 对左中右取最大
int leftSum=recur(nums,l,mid);
int rightSum=recur(nums,mid+1,r);
// cross的计算不是原问题规模更小的问题 是合并的一部分
int crossSum=midSum(nums, l, r, mid);
int res=Math.max(leftSum, Math.max(rightSum, crossSum));
return res;
}
// 求中间子串: 这个求和不是原问题的子问题(必须 跨越中点) 所以不用recur()计算
private int midSum(int[] nums, int l, int r,int mid) {
if(l==r) return nums[l];// 只有一个元素
int sumTmp=0,leftSum=Integer.MIN_VALUE;
int rightSum=leftSum;
for(int i=mid;i!=-1;i--) {
sumTmp+=nums[i];
leftSum=Math.max(sumTmp, leftSum);
}// 包括mid位置的左半边最大和
sumTmp=0;
for(int i=mid+1;i!=nums.length;i++) {
sumTmp+=nums[i];
rightSum=Math.max(sumTmp, rightSum);
}// 不包括mid位置的右半边最大和
return leftSum+rightSum;
}
}
分析原问题,发现问题的解无外乎三中情况. 子序列都在在mid的前面, 或子序列都在mid的后面, 或子序列不都在前面也不都在后面, 跨越了mid, 前后都有的, 代码中的midSum
函数就是算这个的.
原问题划分为:
前后半部分都是大问题变小问题经典的形式, 中间部分和不是原问题的同结构子问题, 所以是属于分治算法的合并部分
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
public class Solution {
public boolean canJumpFromPosition(int position, int[] nums) {
if (position == nums.length - 1) return true;
int furthestJump = Math.min(position + nums[position], nums.length - 1);// 避免跳到超出数据容量处去了
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++) // 从position+1处开始找能跳的点
if (canJumpFromPosition(nextPosition, nums))
return true;
return false;
}
public boolean canJump(int[] nums) {
// 从0开始遍历递归
return canJumpFromPosition(0, nums);
}
}
enum Index { // 设计了一个每局变量
GOOD, BAD, UNKNOWN
}
public class Solution {
Index[] memo;// 判断每一个位置能不能跳到尾巴(end)
public boolean canJump(int[] nums) {
memo = new Index[nums.length];
for (int i = 0; i < memo.length; i++)
memo[i] = Index.UNKNOWN;// 初始化memo数组
memo[memo.length - 1] = Index.GOOD;// 最尾巴位置可以跳
return canJumpFromPosition(0, nums);
}
private boolean canJumpFromPosition(int position, int[] nums) {
// 以下两行就是动态规划自顶向下的精髓(也叫带备忘录的动态规划)
if (memo[position] != Index.UNKNOWN) // 若当前位置可以跳 直接返回
return memo[position] == Index.GOOD ? true : false;
//若对应的子问题没有求解过, 则继续计算
int furthestJump = Math.min(position + nums[position], nums.length - 1);// 避免跳到超出数据容量处去了
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++) {// 从position+1处开始找能跳的点
if (canJumpFromPosition(nextPosition, nums)) {
memo[position] = Index.GOOD;
return true;
}
}
// 找不到能跳的点
memo[position] = Index.BAD;
return false;
}
public boolean canJump2(int[] nums) {
// can数组标识当前位置是否能跳到尾巴(end)
boolean[] can=new boolean[nums.length];// 默认为false
can[nums.length-1]=true;// 最尾为真(可跳)
int jumpMax=0;//最远可跳的距离
for(int i=nums.length-2;i>=0;i--) {//从len-2向前遍历
//i+nums[i]是i处可跳的距离
jumpMax=Math.min(i+nums[i], nums.length-1);
for(int j=i+1;j<=jumpMax;j++)//从i+1遍历到jumpMax 看有没有可达尾的
if(can[j])
can[i]=true; break;
}
return can[0];
}
贪心算法通常是自顶向下的算法, 每一步贪心都把当前问题的规模缩减一点
贪心算法在使用时要证明(相当于做了数学归纳法):
优化
如果在贪心算法的每步操作时, 不得不考虑众多选择: 很多时候需要对原问题的输入输入做点排序操作. 便于每步贪心时减少’查找当前问题最优解’的时间复杂度.
public boolean canJump(int[] nums) {
int leftMostIndex=nums.length-1;// 初始化为最右元素
for(int i=nums.length-1;i>=0;i--) {// 从后往前
//每次遍历贪到最右(最大下标)
if(nums[i]+i>=leftMostIndex)//i处的元素够得到 '最左可达元素'
leftMostIndex=i;
}
return leftMostIndex==0;
}
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int currSum = nums[0]; // 每一步的当前最优解
int maxSum = nums[0];
// 从左到右 每步贪心
for (int i = 1; i < len; ++i) {
// 每步都贪出当前 以j结尾的最大值;
currSum = Math.max(nums[i], currSum + nums[i]);
// 更新maxSum
maxSum = Math.max(maxSum, currSum);
}
return maxSum;
}
}
currSum = Math.max(nums[j], currSum + nums[j]);
maxSum = Math.max(maxSum, currSum);
比较时也不会影响maxSum的正确性单源最短路径问题, 最小生成树问题