上周挖的坑来填了

上周说要好好整理这道题的,因为确实花了很多时间去完成它。但是今天准备回顾的时候发现草稿纸一扔有很多细节记不起来了,看来还是要打铁趁热,现在我尽量写清楚自己的解题经过。

53. Maximum Subarray - Easy

Description

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

Example:
Given the array [-2,1,-3,4,-1,2,1,-5,4], the contiguous subarray [4,-1,2,1] has the largest sum = 6, output the sum.

More practice:
If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

解题经过

思路

这道题要说是easy我认为是不太合适的,对着那个数组用眼睛扫一下,人脑似乎很容易判断哪个子串最大,但机器呢?遍历所有长度取值的可能子串吗,不用想了,肯定超时的。
最近学了分治的方法,这道题也是在leetcode上搜索分治出来的,所以分治有什么好的解决办法呢?我就一头扎下去往这方面想,怎样划分成规模更小的子问题,然后把他们的结果合并起来?

第一种想法

这是失败的尝试。我想的是,先把数组分成前后两半,分别找到两边的最大子串,然后再看二者是否能合并。这个想法并不容易实现,我遇到了很多的问题,比如怎样才算能合并——左边最大子串往右至右边最大子串往左这一段再算一次最大子串,而三个子串间隔的两段合并成两个负数(一定是负数,要么是空串),这五个值去作比较结果就比较明显了(写出来还是一套复杂的if else)。
总之,很难写,很多细节要想清楚和处理。我写第一版转成了链表结构,写了80多行嫌弃迭代器操作太多,然后第二版用vector下标操作舒服多了,然而写到最后的逻辑判断的时候也80多行了,却发现了一个重大漏洞,额,记不起来了。

第二种想法

这种时候已经非常受挫了,只是隐约觉得非要把整个数组割开来好像不太对,就换了一种。但这种做法仍然没有跳出一个死胡同:我的方法一直是基于合并,合并地越长越好。
合并的时候可以利用一些规律:

  • 相邻的同号数字可以合并成更大/小的,于是整个数组可以变成连续的正负正负的形式;
  • 头尾的负数肯定会被丢弃;
  • 两个正数之间隔的负数绝对值如果同时小于两个整数,那么这两个整数就可以合并。

这样一趟趟去合并,每次也能把问题的规模减小,每次集中的处理三个相邻的数,这样也算是分治了。看一下我的主要递归代码:

int fun(vector<int>& nums) { //正着一遍,倒过来再一遍才能保证最后只剩一个数
        if(nums.size() == 1) return nums[0];

        vector<int> anotherNums = merge(nums);
        reverse(anotherNums.begin(), anotherNums.end());
        vector<int> newNums = merge(anotherNums);

        return fun(newNums);
    }

艰难地实现

一边写一边会发现很多细节问题,比如全为负数的情况要特殊处理,合并的过程判断的逻辑也要不断完善。最后是靠着那个长为1000的样例数组,不断测试和改bug出来的代码,最后有90行。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //预处理,保证递归前和递归返回结果都是正负正串的形式
        vector<int> numsList;
        bool sign = (nums[0] >= 0); //true代表正或0,false代表负
        int temp = nums[0];
        for(int i = 1; i < nums.size(); ++i) {
            if((nums[i] >= 0) == sign) temp += nums[i];
            else {
                numsList.push_back(temp);
                sign = (nums[i] >= 0);
                temp = nums[i];
            }
        }
        numsList.push_back(temp);
        if(numsList[0] < 0) {
            vector<int> temp (numsList.begin() + 1, numsList.end());
            numsList = temp;
        }
        if(numsList.back() < 0) numsList.pop_back();
        //要顺便处理一种特殊情况,即全为负数
        if(numsList.empty()) {
            int Max = nums[0];
            for(int i = 1; i < nums.size(); ++i)
                Max = max(Max, nums[i]);
            return Max;
        }

        return fun(numsList);
    }

    int fun(vector<int>& nums) {
        if(nums.size() == 1) return nums[0];

        vector<int> anotherNums = merge(nums);
        reverse(anotherNums.begin(), anotherNums.end());
        vector<int> newNums = merge(anotherNums);

        return fun(newNums);
    }

    vector<int> merge(vector<int> nums) {
        vector<int> newNums;
        int left = nums[0];
        bool lessThanLeft = false;
        for(int i = 1; i < nums.size(); i += 2) {
            int middle = nums[i];
            int right = nums[i + 1];
            if(left + middle >= 0 && middle + right >= 0) {
                if(!newNums.empty() && newNums.back() > 0) newNums.push_back(nums[i - 2]);
                left = left + middle + right;
                lessThanLeft = false;
                continue;
            }
            if(left <= right) {
                if(newNums.empty()) {
                    left = right;
                    continue;
                }
                if(lessThanLeft) {
                    newNums.push_back(nums[i] + nums[i - 1] + nums[i - 2]);
                    lessThanLeft = false;
                    left = right;
                }
                else {
                    if(!newNums.empty() && newNums.back() < 0) newNums.push_back(left);
                    else {
                        newNums.push_back(nums[i - 2]);
                        newNums.push_back(nums[i - 1]);
                    }
                    left = right;
                }
            }
            else {
                if(!newNums.empty() && newNums.back() > 0) newNums.push_back(nums[i - 2]);
                newNums.push_back(left);
                left = right;
                lessThanLeft = true;
            }
        }
        if(newNums.empty() || newNums.back() < 0) newNums.push_back(left);
        else {
            newNums.push_back(nums[nums.size() - 2]);
            newNums.push_back(nums[nums.size() - 1]);
        }

        return newNums;
    }
};

反思

1.我试着去分析自己算法的复杂度,发现不行,它太依赖于输入数组的排列情况。最好的一遍扫过就可以了,时间上还是不错的。
2.写出来太辛苦了,只当是一种锻炼吧。
3.写出来太辛苦了,下次不要钻牛角尖了,及时放弃做别的题比较好。要想思路开阔也要有个积累的过程。

借鉴

也看了别人的解法,最高票的解法据说是贪心算法的思想,以及看了另一个十几行的分治算法,都很巧妙。我也不知道吸收了多少东西,先这样吧哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值