在线算法整理

在线算法

在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。

最大连续子序列和(53)

leetcode

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 

1.动态规划算法

状态转移方程

f(i)=max{f(i−1)+nums[i],nums[i]}

考虑到 f(i)只和 f(i-1)相关,于是可以只用一个变量pre 来维护对于当前 f(i)从而让空间复杂度降低到 O(1)

https://leetcode.cn/problems/maximum-subarray/solution/zui-da-zi-xu-he-by-leetcode-solution/

var maxSubArray = function(nums) {
    let pre = 0, maxAns = nums[0];
    nums.forEach((x) => {
        pre = Math.max(pre + x, x);
        maxAns = Math.max(maxAns, pre);
    });
    return maxAns;
};

如果要输出该序列

用一个变量arr去记录最大和连续序列的起始与结束索引。随着最大值max的更新,结束索引也更新。当当前和小于等于零,更新开始索引。

var maxSubArray = function (nums) {
    let arr = [0, 0];
    let max = -101;
    let cur = 0;
    let temp = 0;
    for (let i = 0; i < nums.length; i++) {
        if (cur <= 0) {
            cur = 0;//连续性断了,重新开始
            temp = i;
        }
        cur += nums[i];
        if (cur > max) {
            arr[0] = temp;
            max = cur;
            arr[1] = i;
        }
    }
    //arr[0]为最大子序和的起始索引,arr[1]为最大子序和的结束索引
};

2.分治算法

leetcode

分治算法将大问题拆分为小问题,再组合起来得到大问题的解。

分:以数组中点为界限,拆成两个数组left,right;

合:

  • 这里求一个数组的最大连续和mSum,那么这个最大连续和区间可能出现在left、right、横跨left,right两个数组。

  • left数组的最大连续和、right数组的最大连续和通过递归可以得到,递归结束条件为数组中只有一个元素。

  • 当最大连续和区间横跨left,right两个数组时,最大连续和=(以left数组右端点作为区间结束端点时的最大连续和)+(以right数组左端点作为区间开始端点时的最大连续和)。

  • 因此mSum为max(left数组的最大连续和、right数组的最大连续和、横跨时的最大连续和)

function Status(l, r, m, i) {
    this.lSum = l;
    this.rSum = r;
    this.mSum = m;
    this.iSum = i;
}

const pushUp = (l, r) => {
    const iSum = l.iSum + r.iSum;
    const lSum = Math.max(l.lSum, l.iSum + r.lSum);
    const rSum = Math.max(r.rSum, r.iSum + l.rSum);
    const mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
    return new Status(lSum, rSum, mSum, iSum);
}

const getInfo = (a, l, r) => {
    if (l === r) {
        return new Status(a[l], a[l], a[l], a[l]);
    }
    const m = (l + r) >> 1;
    const lSub = getInfo(a, l, m);
    const rSub = getInfo(a, m + 1, r);
    return pushUp(lSub, rSub);
}

var maxSubArray = function(nums) {
    return getInfo(nums, 0, nums.length - 1).mSum;
};

最大连续子序列乘积(156)

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

1.动态规划算法

如果我们用 f*max(i) 来表示以第 i 个元素结尾的乘积最大子数组的乘积, 考虑元素的正负性,如果第i个元素是正数,则希望该元素能够乘上一个大的正值,如果第i个元素是负数,则希望该元素能乘上一个小的负数。所以除了记录最大乘积,还需要记录最小乘积,毕竟当nums[i]是负数的时候,有可能翻盘。

转移方程:
f m a x ( i ) = m a x ( f m a x ( i − 1 ) ∗ n u m s [ i ] , f m i n ( i − 1 ) ∗ n u m s [ i ] , n u m s [ i ] ) fmax(i)=max{(fmax(i−1)*nums[i], fmin(i−1)*nums[i],nums[i] )} fmax(i)=max(fmax(i1)nums[i],fmin(i1)nums[i],nums[i])

f m i n ( i ) = m i n ( f m a x ( i − 1 ) ∗ n u m s [ i ] , f m i n ( i − 1 ) ∗ n u m s [ i ] , n u m s [ i ] ) fmin(i)=min{(fmax(i−1)*nums[i], fmin(i−1)*nums[i], nums[i])} fmin(i)=min(fmax(i1)nums[i],fmin(i1)nums[i],nums[i])

var maxProduct = function (nums) {
    let max=1, min=1;
    let result=nums[0];
    for(let i =0;i<nums.length;i++){
        let temp = max;
        max=Math.max(max*nums[i],min*nums[i],nums[i]);
        min=Math.min(temp*nums[i],min*nums[i],nums[i]);
        result=Math.max(max,result);
    }
    return result;
};

最大子串和

对于一个包含负值的数字串array[1…n],要找到他的一个子串array[i…j](0<=i<=j<=n),使得在array的所有子串中,array[i…j]的和最大。(子串不要求连续,只要元素在原数组中的顺序一致

Kadane算法

原作者

首先,对于array[1…n],如果array[i…j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i…k]的和大于0。因为如果存在k使得array[i…k]的和小于0,那么我们就有array[k+1…j]的和大于array[i…j],这与我们假设的array[i…j]就是array中和最大子串矛盾。

其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i…j]和任意k(i<=k<j),有array[i…k]的和大于0。(此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p…q],是array的和最大子串,且array[p…q],跨越了array[i…j],array[j+1…k]。根据我们的分组方式,存在i<=m<j使得array[i…m]的和是array[i…j]中的最大值,存在j+1<=n<k使得array[j+1…n]的和是array[j+1…k]的最大值。由于array[m+1…j]使得array[i…j]的和小于0。此时我们可以比较array[i…m]和array[j+1…n],如果array[i…m]的和大于array[j+1…n]则array[i…m]>array[p…q],否array[j+1…n]>array[p…q],无论谁大,我们都可以找到比array[p…q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p…q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用)。

根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。

function Kadane(array, length, left, right)
{ 
	let cur_max = max = left = right = cur_left = cur_right = 0;
 
	for(let i = 0; i < length; ++i)
	{
		cur_max += array[i];//记录前缀和
		if(cur_max > 0)
		{
			cur_right = i;
			if(max < cur_max)
			{
				max = cur_max;//更新最大值
				left = cur_left;//移动子串的指针
				right = cur_right;
			}
		}
		else
		{
            //分割子串
			cur_max = 0;
			cur_left = cur_right = i + 1;
		}
	}
	return max;
}

k次串联后最大子序列和(1191)

最大子序和变形

原作者

以arr=[1,2,-1],k=3为例

12-112-112-1

当k=2时若最大连续和数组横跨两个序列,此时最大连续和为arr的最大前缀和(2)+arr的最大后缀和(3)=5。若不横跨两个序列,则最大连续和就是arr的最大连续和=3。所以二次串联后的最大连续和数组为【1,2,-1,1,2】。

k>2时就是吧【1,2,-1】往前补位,也只是k为2的结果最后加上(k-2)*sum,实际上是上一个情况的变种。

1(最大后缀和2索引)2(后缀)-1(后缀)1(前缀)2(最大前缀和3索引)-1
var modulo = (10 ** 9) + 7;
/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number}
 */
var kConcatenationMaxSum = function (arr, k) {
    let prefix = arr[0]
    let preMax = Math.max(0, prefix);
    let postfix = arr[arr.length - 1];
    let postMax = Math.max(0, postfix);
    let subSum = Math.max(0, arr[0]);
    let subMax = Math.max(0, subSum);
    // 求最大前缀和以及最大后缀和以及最大子串和
    for (let i = 1; i < arr.length; i++) {
        subSum = Math.max(0, subSum + arr[i]) % modulo;
        prefix = prefix + arr[i];
        postfix = postfix + arr[arr.length - i - 1];
        if (subSum > subMax) {
            subMax = subSum;
        }
        if (prefix > preMax) {
            preMax = prefix
        }
        if (postfix > postMax) {
            postMax = postfix
        }
    }
    let max = 0;
    if (k > 1) {
        max = (postMax + preMax) % modulo;
        if (prefix > 0 && k > 2) {
            max = (max + prefix * (k - 2)) % modulo;
        }
    }
    if(subMax*k % modulo == max) return max;//添加这个是因为leetcode增加了一个很大的数组
    return max < subMax ? subMax : max;
};

插入排序算法

对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
// 插入排序改进:二分插入排序

function BinaryInsertSort(arr, len)   
{   
    let key, left, right, middle;   
    for (let i=1; i<len; i++)   
    {   
        key = a[i];   
        left = 0;   
        right = i-1;   
        //通过二分法先确定待排元素要插入的位置,
        //普通二分法每次比较后都要进行移位
        while (left<=right)   
        {   
            middle = (left+right)/2;   
            if (a[middle]>key)  right = middle-1; 
            else  left = middle+1;  
        }   
        for(let j=i-1; j>=left; j--) a[j+1] = a[j];//将排序元素逐步向后挪
        a[left] = key;          
    }   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值