大概有十几天没有做题了,主要是出差大概耽误了一周左右的时间,进度又跟不上了。
17.6.19 晚
LintCode Next PermutationsII
给定一个序列,求其下一个序列。比如123下一个序列就是132,如果已经是最大的序列321,那么会回到最小的123。
思路:从序列的末尾开始看起,如果对于位置i的数,从i+1~n-1的数全部小于位置i的数,那么说明从位置i开始的数就是最大的。如果发现一个不是,就和位置i的数交换,然后再把位置i+1~n-1的数重新排序一下。
可能我说的比较抽象,贴一个带图的博客:http://www.cnblogs.com/grandyang/p/4428207.html
代码:
void nextPermutation(vector<int> &nums) {
// write your code here
int n = nums.size(), idx = 0;
for (int i = n-1; i >= 0; i--) {
for (int j = n-1; j > i; j--) {
if (nums[i] >= nums[j]) continue;
swap(nums[i], nums[j]);
sort(nums.begin()+i+1, nums.end());
return;
}
}
sort(nums.begin(), nums.end());//若序列是降序排列的,那么其下一个序列就是初始的最小序列。
}
LintCode Number of Airplanes in the Sky
抽象成数学语言就是,给定一些interval,求其overlap最多的个数。
一开始想到的就是用hash来做,对于[start, end]内的所有数其hash++,最后找到最大的那个数。但是超时了,因为其实对于一个Interval来说,只有start和end两端有意义,中间的数并没有多少的意义,所以上述做法其实是做了大量的无用功。
经百度后,发现用 扫描线做法。就是把所有start,end这些时间点按照时间先后排序,同时要标记其是start还是end。最后线性遍历所有时间,如果是start就计数器就++,否则就–,找出这个过程中的最大值。
代码:
bool cmp(pair<int, int> p1, pair<int, int> p2) {
if (p1.first != p2.first) return p1.first < p2.first;
return p1.second < p2.second;//意思是如果时间点相同,那么要优先下降。
}
class Solution {
public:
/**
* @param intervals: An interval array
* @return: Count of airplanes are in the sky.
*/
int countOfAirplanes(vector<Interval> &airplanes) {
// write your code here
vector<pair<int, int>> v;
for (auto interval: airplanes) {
v.push_back(make_pair(interval.start, 1));
v.push_back(make_pair(interval.end, 0));
}
sort(v.begin(), v.end(), cmp);
int ret = 0, cnt = 0;
for (auto vv : v) {
vv.second == 1 ? cnt++ : cnt--;
ret = max(ret, cnt);
}
return ret;
}
};
LintCode Reorder array to construct the minimum number
很相似的题目,在PAT中应该遇到过。对于两个string A和B,如果A+B < B+A,那么就 认为 A < B。
排序之后遍历一遍把所有的string加起来。注意处理前导0的情况。
bool cmp(string A, string B) {
string C = A+B;
string D = B+A;
return C < D;
}
string int2str(int num) {
string ret = "";
if (num == 0) ret = "0";
while (num) {
ret += (num%10 + '0');
num /= 10;
}
reverse(ret.begin(), ret.end());
return ret;
}
class Solution {
public:
/**
* @param nums n non-negative integer array
* @return a string
*/
string minNumber(vector<int>& nums) {
// Write your code here
string ret = "";
int len = nums.size();
vector<string> v;
for (auto n: nums) {
v.push_back(int2str(n));
}
sort(v.begin(), v.end(), cmp);
for (auto vv: v) {
ret += vv;
}
//处理前导0
for (int i = 0; i < ret.size(); i++) {
if (ret[i] == '0' && i != ret.size()-1) continue;
ret = ret.substr(i);
break;
}
return ret;
}
};
6.20晚 战绩1/4
LintCode Reverse Pairs
给定一个数组A,如果i < j 且 A[i] > A[j],那么称(A[i], A[j])是一对翻转对。找出所有翻转对的个数。
思路:brute-force以O(n^2)的成绩超时。那么就需要优化,常见的思路就是按照O(n^2)->O(nlogn)->O(n)->O(logn)这样的思路来进行优化。
思路1:O(nlogn)
我们另开一个数组,从尾部到头遍历数组A,每遍历到一个位置i时,就把该数i利用二分搜索法插到新的数组中。这样插入之后该数组左边的数的个数就是翻转对的个数。
注意: 需要考虑重复元素的情况,所以插入该数时要始终把其插入最左边才能不考虑重复元素。
代码:
long long reversePairs(vector<int>& A) {
// Write your code here
vector<int> v;
long long ret = 0;
if (A.size() == 0) return ret;
for (int i = A.size()-1; i >= 0; i--) {
int left = 0, right = v.size();
while (left < right) {
int mid = (left+right)>>1;
if (A[i] > v[mid]) left = mid+1;
else right = mid; //当A[i]<= mid时,right=mid,这样可以保证遇到相同元素往最左边进行寻找
}
v.insert(v.begin()+right, A[i]);
ret += right;//该数的插入下标为index,其左边0~right-1共right个数就是right个翻转对
}
return ret;
}
思路2:来自九章的Merge Sort思想
MergeSort的思想就是递归的把左半部分MergeSort,然后把右半部分MergeSort,最后把两段有序的数组Merge起来。由于左右部分已经有序,所以若左边部分位置i的值大于右边部分右边j的值,那么左边部分i以后的值也都大于右边部分j的值,所以翻转对的个数就是左半部分中位置i以及位置i右边的数的个数。然后这部分已经求解过了,便将其排序使其有序参与更大部分的求解。
long long reversePairs(vector<int>& A) {
// Write your code here
int n = A.size();
tmp = new int[n];
return mergeSort(A, 0, n-1);
}
long long merge(vector<int> &A, int l, int m, int r) {
int i = l, j = m + 1, k = l;
long long ans = 0;
while (i <= m && j <= r) {
if (A[i] > A[j]) {
tmp[k++] = A[j++];
ans += m - i + 1;
} else {
tmp[k++] = A[i++];
}
}
while (i <= m) tmp[k++] = A[i++];
while (j <= r) tmp[k++] = A[j++];
for (i = l;i <= r; ++i)
A[i] = tmp[i];
return ans;
}
long long mergeSort(vector<int> &A, int l,int r) {
long long ans = 0;
if (l < r) {
int m = (l + r) >> 1;
ans += mergeSort(A, l, m);
ans += mergeSort(A, m + 1, r);
ans += merge(A, l, m, r);
}
return ans;
}
LintCode Search for a Range
给定一个有序序列和一个目标数target,让你找出这个序列中等于target数的区间(即有重复的数)。找不到target则返回-1。
要求时间复杂度为O(logn)。
思路:典型的二分搜索的题目。思路就是用两次二分查找,一次查找区间的左边界,一次查找区间的右边界即可。
代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> res(2, -1);
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
if (nums[right] != target) return res;
res[0] = right;
right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) left = mid + 1;
else right= mid;
}
res[1] = left - 1;
return res;
}
};
被二分法很多的细节地方都弄晕了,比如加不加等号,考不考虑相等的情况,边界怎么写等等很多细节,刚好发现一篇很好的博客:http://www.cnblogs.com/grandyang/p/6854825.html
因为懒得注册所以没有评论,但还是感谢上面博主的指点!
常见的二分查找主要分为如下3种情况:
1.查找给定的数,找不到则返回-1。
int find(vector<int>& nums, int target) {
if (nums.size() == 0) return -1;
int left = 0, right = nums.size()-1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1;
else right = mid+1;
}
return -1;
}
2.查找序列中第一个不小于目标值的数,返回其下标。
这种情形又可分为两种情形:1.序列中没有目标值 2.序列中有多个目标值。显然这种情形下nums[mid] == target这条语句就没有多少作用了,因为就算判断出了nums[mid] == target也得不到答案。
int find(vector<int>& nums, int target) {
int left = 0, right = nums.size();//这里right不能声明成nums.size()-1;
while (left < right) {//left == right时跳出循环
int mid = (left + right) >> 1;
if (nums[mid] < target) left = mid + 1;
else right = mid;//right所指的值永远是不小于目标值的
}
return right;
}
那么right-1就是第一个小于目标值的下标。
3.查找序列中第一个大于目标值的数,返回其下标。
思路和2差不多,只需要改变一个符号,就能改变搜索方向。
int find(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) left = mid + 1; //把<改为<=
else right = mid;
}
return right;
}
那么right-1就是最后一个小于等于目标值的数了。
关于这道题,我还发现了相同思路的另外一种写法,区别就是二分法的细节不同,所以也来贴一下。
public int[] searchRange(int[] A, int target) {
int[] res = {-1,-1};
if(A==null || A.length==0) {
return res;
}
int ll = 0;
int lr = A.length-1;
while(ll<=lr) {
int m = (ll+lr)/2;
if(A[m]<target) {
ll = m+1;
} else {
lr = m-1;
}
}
int rl = 0;
int rr = A.length-1;
while(rl<=rr) {
int m = (rl+rr)/2;
if(A[m]<=target) {
rl = m+1;
}
else {
rr = m-1;
}
}
if(ll<=rr) {
res[0] = ll;
res[1] = rr;
}
return res;
}
LintCode Search in Rotate Array I
给定一个旋转过的有序序列,请用O(logn)的方法找到target。
思路: 二分搜索法。由于平时我们的二分搜索都是在有序序列上的,虽然这道题给的序列并不有序,但是它的一部分是有序的。所以我们可以在有序的子序列上进行二分查找。
先贴代码:
int search(vector<int> &A, int target) {
// write your code here
int n = A.size();
if (n == 0) return -1;
int l = 0, h = n-1;
while (l <= h) {
int mid = (l+h) >> 1;
if (A[mid] == target) return mid;
if (A[l] < A[mid]) { //说明mid的左半部分都是有序的
if (A[l] <= target && A[mid] > target) h = mid-1;//如果target在有序的部分上
else l = mid+1; //否则就从右边开始找
} else {//同理,说明右半部分是有序的
if (A[mid] < target && A[h] >= target) l = mid+1;
else h = mid-1;
}
}
return -1;
}
LintCode Search in Rotate Array II
和上一题不同的是,本题的元素可以重复。那么会带来什么影响呢?也就是在判断哪部分有序的时候出现了A[l] == A[mid]的情况,这种情况下无法判断哪部分是有序的。但稍微改变一下就能解决这个问题。当A[l] == A[mid]时,我只要l++即可(即丢弃这个元素,因为这个元素并不等于target,也不会改变原有的顺序信息,所以丢弃掉)。
bool search(vector<int> &A, int target) {
// write your code here
int n = A.size();
if (n == 0) return false;
int l = 0, h = n-1;
while (l <= h) {
int mid = (l+h) >> 1;
if (A[mid] == target) return true;
if (A[l] < A[mid]) {
if (A[l] <= target && A[mid] > target) h = mid-1;
else l = mid+1;
} else if (A[l] > A[mid]) {
if (A[mid] < target && A[h] >= target) l = mid+1;
else h = mid-1;
} else {
l++;
}
}
return false;
}
6.22晚
LintCode Sort Colors
给定一个序列分别由0, 1, 2组成,要求把这个序列排序,按照0,1,2的顺序。
不能使用库sort。
思路1:二次遍历,统计0,1,2的个数,然后在按照个数复写该序列。
思路2:一次遍历,同时用两个指针分别指向开头和末尾,遍历到0时,就和头指针所指元素交换,然后头指针+1,遍历到2时,就和尾元素交换,然后尾元素-1。注意和尾元素交换过来的元素还需要继续判断,而和头元素交换则不用判断。
void sortColors(vector<int> &nums) {
// write your code here
if (nums.size() == 0) return;
int zero, one, two;
zero = one = 0, two = nums.size()-1;
while (one <= two) {
if (nums[one] == 1) {
one++;
}
else if (nums[one] == 0) {
swap(nums[one++], nums[zero++]);
}
else if (nums[one] == 2) {
swap(nums[one], nums[two--]);
}
}
}
总结:自己也一直在往遍历然后交换元素上去想,可总是差点意思。或者说想的复杂了一点,导致没能做出来。
LintCode Spiral Matrix
很经典的题目了,绕圈输出矩阵。考察基本功吧,注意细节即可。
vector<int> spiralOrder(vector<vector<int>>& matrix) {
// Write your code here
vector<int> ret;
if (matrix.size() == 0 || matrix[0].size() == 0) return ret;
int up = 0, down = matrix.size()-1, left = 0, right = matrix[0].size()-1;
while (up <= down && left <= right) {
for (int i = left; i <= right; i++) {
ret.push_back(matrix[up][i]);
}
for (int i = up+1; i <= down; i++) {
ret.push_back(matrix[i][right]);
}
//下面的判断条件要多一个,防止重复输出,比如只有一行的情形
for (int i = right-1; i >= left && up != down; i--) {
ret.push_back(matrix[down][i]);
}
for (int i = down-1; i > up && left != right; i--) {
ret.push_back(matrix[i][left]);
}
up++,down--,left++,right--;
}
return ret;
}
LintCode Spiral Matrix II
和1差不多,不过比1好像还简单一点。
vector<vector<int>> generateMatrix(int n) {
// Write your code here
vector<vector<int>> ret(n, vector<int>(n, 0));
int cnt = 1;
int up = 0, down = n-1, left = 0, right = n-1;
while (cnt <= n*n) {
for (int i = left; i <= right; i++) {
ret[up][i] = cnt++;
}
for (int i = up+1; i <= down; i++) {
ret[i][right] = cnt++;
}
for (int i = right-1; i >= left; i--) {
ret[down][i] = cnt++;
}
for (int i = down-1; i > up; i--) {
ret[i][left] = cnt++;
}
up++, down--, left++, right--;
}
return ret;
}
LintCode The Smallest Difference
这题简单,排序,然后类似MergeSort的用两个指针来判断。
int smallestDifference(vector<int> &A, vector<int> &B) {
// write your code here
sort(A.begin(), A.end());
sort(B.begin(), B.end());
int pa = 0, pb = 0;
int ret = INT_MAX;
while (pa < A.size() && pb < B.size()) {
ret = min(ret, abs(A[pa] - B[pb]));
if (A[pa] > B[pb]) pb++;
else pa++;
}
return ret;
}
LintCode Trapping Rain Water
乍看没有啥思路,给的提示是用Forward and Backward Traversal 和 Two Pointers来做的。自己画图体验一下就发现了一个point: 用left和right来遍历该数组。left保持不动,向右移动right,如果发现一个heights[right] >= heights[left],那么无论right右边时怎么样,从left到right这一部分的盛水体积都是固定的。那么如果不存在满足条件的right呢?那么便只需要再从右边利用同样遍历原理即可。
先上自己AC的代码:
int trapRainWater(vector<int> &heights) {
// write your code here
int n = heights.size();
if (n <= 2) return 0;
int ret = 0, tmp = 0;
int left = 0, right = 0;
while (right < n) {
//while (left < n-1 && heights[left] <= heights[left+1]) left++;
right = left+1;
for (; right < n && heights[right] < heights[left]; right++) {
tmp += heights[left] - heights[right];
}
if (right < n && heights[right] >= heights[left]) {
ret += tmp;
tmp = 0;
left = right;
}
}
right = n-1;
tmp = 0;
while (right > left) {
//while (right > left && heights[right-1] >= heights[right]) right--;
int l = right - 1;
for (; l >= left && heights[l] < heights[right]; l--) {
tmp += heights[right] - heights[l];
}
if (l >= left && heights[l] >= heights[right]) {
ret += tmp;
tmp = 0;
right = l;
}
}
return ret;
}
嗯。。和别人的比起来果然一如既往的又臭又长。。。不说了,看一下网上大神们的优秀解答吧!
http://www.cnblogs.com/felixfang/p/3713197.html
http://blog.csdn.net/linhuanmars/article/details/20888505
原谅我懒了。。由于后天就回家了,所以暂时也不想写了。回家之后接着把题刷起来就好了。