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个子序列有什么关系呢?很明显就是
也就是说是前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]。
综上所述,最终的递推关系就是:
代码:
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:我粗看了一下,没有看懂。