LintCode 解题记录17.6.26 ~ 17.7.2

6.26

LintCode Wiggle Sort

给定一个未排序的数组,要求按照如下规则原地排序:
nums[0] <= nums[1] >= nums[2] <= nums[3]…
答案会有很多种,输出其中一种就可以。
自己的思路:按照这样的选择规则就可以:首先把数组排序,然后nums[0],nums[2],nums[4]..这些从小到大选,而nums[1],nums[3],nums[5]..这些从大到小选择就可以。由于题中中要求空间复杂度为O(1),所以想到了平移。比如排完序后是[1,2,3,4,5,6],把6先存起来,然后从2到5向右平移一位,然后把6放在原来2的位置。以此循环。
时间复杂度是O(n2),空间复杂度是O(1)。
嗯。。果然永远只想的出最蠢的办法。。:D

其实并不需要按照我那种选择规则,因为显得过于苛刻。把数组排完序后,发现只要交换第2位和第3位、第4位和第5位就可以。因为前面的<=在排序完成后总是能满足,所以只需要满足后面的大于等于号就可以了。
时间复杂度是排序的时间复杂度O(nlogn),空间复杂度为O(1)

    void wiggleSort(vector<int>& nums) {
        // Write your code here
        if (nums.size() == 0) return;
        sort(nums.begin(), nums.end());
        for (int i = 1; i < nums.size()-1; i += 2) {
            swap(nums[i], nums[i+1]);
        }
    }

观察题目给的规则我们发现:
如果i是偶数,那么nums[i] <= nums[i+1];
如果i是奇数,那么nums[i] >= nums[i+1];
只要满足上述两个条件那么就符合题意。所以我可以只需要遍历一遍给的数组,把不符合条件的位置交换一下即可。

    void wiggleSort(vector<int>& nums) {
        // Write your code here
        if (nums.size() == 0) return;
        for (int i = 0; i < nums.size()-1; i++) {
            if (i % 2 == 0 && nums[i] > nums[i+1])
                swap(nums[i], nums[i+1]);
            if (i % 2 == 1 && nums[i] < nums[i+1])
                swap(nums[i], nums[i+1]);
        }
    }

LintCode Candy

菜不成声:D
题目描述:
There are N children standing in a line. Each child is assigned a rating value.
You are giving candies to these children subjected to the following requirements:
1.Each child must have at least one candy.
2.Children with a higher rating get more candies than their neighbors.
What is the minimum candies you must give?
思路:(Greedy && Forward and Backward Traversal)
首先声明一个candy数组,代表每个孩子能得到的candy数目,初始化为1。
1.从左向右遍历一遍数组,如果rate[i+1] > rate[i],那么candy[i+1] = candy[i]+1。
2.再从右向左遍历一遍数组,此时如果rate[i-1] > rate[i] && candy[i-1] < candy[i]+1,那么candy[i-1] = candy[i]+1。

    int candy(vector<int>& ratings) {
        // Write your code here
        if (ratings.size() == 0) return 0;
        vector<int> candy(ratings.size(), 1);
        for (int i = 0; i < ratings.size()-1; i++) {
            if (ratings[i+1] > ratings[i]) candy[i+1] = candy[i]+1;
        }
        for (int i = ratings.size()-1; i > 0; i--) {
            if (ratings[i-1] > ratings[i])
                candy[i-1] = max(candy[i-1], candy[i]+1);
        }
        int ret = 0;
        for (int i = 0; i < candy.size(); i++) {
            ret += candy[i];
        }
        return ret;
    }

6.27

LintCode Largest Rectangle in Histogram

这是一道很经典的面试题。
首先Brute-Force法以O(n2)的时间复杂度解决。但是在big test case的时候以TLE告终。后来去网上搜寻答案,发现了用堆栈的O(n)解法,又好好理解了一遍。弄懂了记录下来。
主要的思路是在于如果连续的矩形高度都是递增的,那么只需要遍历一遍就能够判断出最大面积,比如{2,3,4,5,6},遍历一遍之后最大面积就是max(2*5, 3*4, 4*3, 5*2, 6*1)。但是给定的数组很可能不是递增的,怎么办?于是就用到了堆栈。
如果当前元素大于栈顶元素,就把当前元素入栈。否则,出栈该元素,计算面积,循环直到栈为空或者栈顶元素小于当前元素。这样,出栈计算面积的时候组成的这些图形便都是递增的,可以以O(n)的复杂度得到最大面积。然后,需要在数组最后添加一个元素0,这样可以保证最后可以弹出所有数组元素。
代码:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int res = 0;
        stack<int> s;
        heights.push_back(0);//末尾添加一个0保证可以计算所有元素
        for (int i = 0; i < heights.size(); ++i) {
            while (!s.empty() && heights[s.top()] >= heights[i]) {
                int cur = s.top(); s.pop();
                res = max(res, heights[cur] * (s.empty() ? i : (i - s.top() - 1)));
            }
            s.push(i);
        }
        return res;
    }
};

上述代码中面积那部分计算的代码我刚开始也没有怎么理解,即:
heights[cur] * (s.empty() ? i : (i - s.top()-1))
当前遍历到的位置是i,出栈元素位置是cur,那么我计算矩形面积的底不就是(i-curr)吗?为啥要上面这样写呢?
首先来说为什么s.empty()时矩形的底是i;因为如果栈为空,说明弹出栈的元素和前面的元素相比是最小的。所以它的底就是i。但如果是i-curr这种计算,此时它的底就只能是1了。同理如果堆栈不为空,也不能用i-curr,只能用i-s.top()-1,比如{1,2,7,6}。

LintCode Maximal Rectangle

这道题是上一道题的变型,可以把每一行都看成是一个直方图的起点,然后不断改变列就能得到相应直方图的高度。
比如题目给的测试用例中,
以第一列为起点,那么每一行对应的直方图高度就是[1,0,0,0,0],也就是第一列。可以算出此时的最大面积是1。
以第二列为起点,那么从第二列往左边看过去,直方图的高度就是[2,1,0,0,0],可以算出此时的最大面积是2。
以此类推,当跑到最后一列时,从最后一列左边看过去就是[1,1,3,3,1],最大面积就是6。
ps:我第一遍是这样看的,比如以第一列为起点,然后向右看,找出此时连续1的个数即直方图的高度。这样做的话最终超时了,因为你数直方图高度的时候不断的重复遍历右半部分。但是当你向左边看的时候,只需要关心上一列是什么情况就好了,因此就省掉了求直方图高度的这一过程。
用Windows自带的画图画个图感受一下:
这里写图片描述

代码:

    int maximalRectangle(vector<vector<bool> > &matrix) {
        // Write your code here
        if (matrix.size() == 0 || matrix[0].size() == 0) return 0;
        int res = 0;
        int row = matrix.size(), col = matrix[0].size();
        vector<int> height(row, 0);
        for (int i = 0; i < col; i++) {
            for (int j = 0; j < row; j++) {
                if (matrix[j][i] == 1) height[j] += 1;
                else height[j] = 0;
            }
            height.push_back(0);
            stack<int> s;
            for (int j = 0; j < height.size(); j++) {
                while (!s.empty() && height[s.top()] >= height[j]) {
                    int curr = s.top(); s.pop();
                    res = max(res, height[curr] * (s.empty() ? j : j - s.top()-1));
                }
                s.push(j);
            }
        }
        return res;
    }

总结:这两道题Hard题我都没有做出来..只好不断的总结、反思、复习回顾,然后提高自己。

6.29

LintCode Maximum SubarrayIII

最大子序列和的第三种变种。先简要回顾一下前面两种:
1.Maximum SubarrayI: O(n)时间复杂度求给定序列的最大子序列和。动态规划,用dp[i]表示遍历到第i个数时的最大子序列和,那么递推关系就是dp[i] = max(dp[i-1]+nums[i], nums[i]);可以只用一个变量s来替换掉dp[i],那么就是s = max(s+nums[i], nums[i]);遍历过程中s的最大值就是我们想要的答案。
2.Maximum SubarrayII:O(n)时间复杂度求给定序列的两个不重叠的最大子序列和。需要O(n)的空间复杂度,思路就是顺序遍历一遍把到位置i为止的最大子序列和存入lmax[i]中,倒序遍历一遍把从i+1到n-1的最大子序列和存入rmax[i]中,最后再遍历一遍找出lmax+rmax的最大值就是我们想要的答案。时间复杂度是O(3n) = O(n)。

  这道题就是上面第二道题的变型,就是找出给定序列的k个不重叠的最大子序列和。至于为什么是动态规划题目我暂时还不是很清楚,首先就设状态变量dp[i][k]是前i个数的k个子序列的最大和。注意是前i个数,也就是说最后一个数是nums[i-1],这么设状态的原因是边界条件比较容易判断(我私以为)。
  那么就要找到该状态和前面状态的递推关系,那么就可以考虑到前i个数k个子序列和前j个数k-1个子序列有什么关系呢?很明显就是

dp[i][k] = dp[j][k-1]+maxSubarray(nums, j-1, i-1)

也就是说是前i个数k个子序列的最大和就是前j个数k-1个子序列加上nums[j]到nums[i-1]的最大子序列和。j的范围又是多少呢?根据题目要求,每一个子序列至少有一个数,j最小就是k-1个序列每个序列只有一个数,其下一个数是nums[k-1];最大就是第k个序列只有一个数,即nums[i-1],所以j的范围就是[k-1, i-1]。
综上所述,最终的递推关系就是:
dp[i][k] = max(dp[i][k], dp[j][k-1]+maxSubarray(nums, k-1, i-1));

代码:

    int maxSubArray(vector<int> nums, int k) {
        // write your code here
        if (nums.size() < k) return 0;
        int n = nums.size();
        vector<vector<int>> dp(n+1, vector<int>(k+1, INT_MIN));
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                int tmp = 0;
                for (int p = i-1; p >= j-1; p--) {
                    tmp = max(tmp + nums[p], nums[p]);
                    dp[i][j] = max(dp[i][j], tmp+dp[p][j-1]);
                }
            }
        }
        return dp[n][k];
    }

注意的地方就是初始化成INT_MIN,然后边界条件初始化为0;求从p到i-1的最大子序列和时要采用倒序遍历。

  类似于背包问题中空间复杂度的优化,可以使用一维数组来代替二维从而将空间复杂度优化到O(n)。
此代码来自网上,并非我写。

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @param k: An integer denote to find k non-overlapping subarrays
     * @return: An integer denote the sum of max k non-overlapping subarrays
     */
    int maxSubArray(vector<int> nums, int k) {
        // write your code here
        int n = nums.size();
        vector<int> dp(n + 1, 0);
        for (int j = 1; j <= k; ++j) {
            for (int i = n; i >= j; --i) {
                dp[i] = INT_MIN;
                int tmp = 0;
                for (int t = i - 1; t >= j - 1; --t) {
                    tmp = max(tmp + nums[t], nums[t]);
                    dp[i] = max(dp[i], tmp + dp[t]);
                }
            }
        }
        return dp[n];
    }
};

  另外九章上提供了两种方法,链接:
http://www.jiuzhang.com/solution/maximum-subarray-iii/
ps:我粗看了一下,没有看懂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值