1、OJ189 rotate array
数组旋转,如给定数组[1,2,3,4,5,6],给定k,如k=2,则结果为[5,6,1,2,3,4],倒数K个节点排在数组前边
数组旋转的方法是:三次旋转
步骤:
1、对倒数K个数先逆序,结果为[1,2,3,4,6,5]
2、对前边的数也做逆序,结果为[4,3,2,1,6,5]
3、再整体逆序,结果为[5,6,1,2,3,4]
OJ189代码:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.empty()) {
return;
}
k = k % nums.size();
int st = 0, ed = nums.size() - 1;
while (st < ed) {
int tmp = nums[st];
nums[st] = nums[ed];
nums[ed] = tmp;
++st;
--ed;
}
st = 0;
ed = k - 1;
while (st < ed) {
int tmp = nums[st];
nums[st] = nums[ed];
nums[ed] = tmp;
++st;
--ed;
}
st = k;
ed = nums.size() - 1;
while (st < ed) {
int tmp = nums[st];
nums[st] = nums[ed];
nums[ed] = tmp;
++st;
--ed;
}
}
};
2、OJ33 search in rotate sorted array
如一个已经选择了的数组[4,5,6,1,2,3],寻找5,数组内元素无重复;
本题是考察:数组类题一个重要方法的运用,局部有序情况下如何使用二分查找;
如4、5、6、1、2、3,寻找5,;
理解局部二分就是理解如下点:
0、局部二分的核心思想是:虽然当前是局部,但力图找到确定的查找范围,如果能找到当然好,不能找到也能找到哪一边肯定不对,那么就去找另一边
1、旋转数组左半部分,是数组值较大的部分;
2、二分后,如果中点就等于目标值,那就直接找到了;
如果中点值大于最左边的值,说明mid是落在旋转数组的部分,即"4、5、6"的部分里,此时应查看,目标值是否在4-6这个区间内
如果目标值就在这个范围内,那么说明就找到了确定的查找范围,在4-6的范围内查找
如果不在这个范围,那么至少说明这个范围不对,换mid以后的范围即[mid+1,right]接着做局部二分查找
如果中点值小于最左边的值,说明mid落在了未旋转的部分,即"1、2、3"的部分里,此时应查看,目标值是否在1-3区间内
如果在,说明找到了确定的查找范围,在1-3内查找
如不在,说明不是这个范围,在[left, mid-1]接着做局部二分查找
OJ33代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.empty()) {
return -1;
}
//局部有序如何使用二分: 或者说如何在二分查找中, 不断确定正确的查找范围;
//如果mid命中在属于左半部的有序区间(旋转的部分), 这时, 如果目标指介于mid和最左值之间, 则范围确定为最左到mid-1之间;
// 如果不是这样, 说明目标值至少不在最左到mid-1内, 则在mid+1到最右去查;
//如果mid落在未旋转的部分即右半部, 这时, 如果目标值介于mid和最右值之间, 则范围确定为mid+1到最右之间;
// 如果不是这样, 说明目标值至少不在mid+1到最右内, 则在left到mid-1去查;
//旋转数组查找的局部二分的思想是: 设法找到目标值明确所在的范围, 实现完全二分查找
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = (left + right)/2;
if (target == nums[mid]) {
return mid;
}
if (nums[mid] >= nums[left]) {
if (target >= nums[left] && target <= nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target >= nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
};
3、OJ81 search in rotate sorted array II
与OJ33区别是,数组内可能有重复的元素
措施为:当发现mid的值与最左和最右都相同,说明是类似[3,3,3,1,2,3,3,3,3,3,3,3,3,3,3,3]这样的情况,需要缩小最左到最右的距离;OJ81比OJ33多出的部分,就针对类似这种情况的处理;处理方式是:最左向右移,最右向左移;
OJ81代码:
class Solution {
public:
bool search(vector<int>& nums, int target) {
if (nums.empty()) {
return false;
}
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = (left + right)/2;
if (nums[mid] == target) {
return true;
}
//对付类似[3,3,3,1,2,3,3,3,3,3,3,3,3,3,3,3]这样的情况
if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
++left;
--right;
continue;
}
if (nums[mid] >= nums[left]) {
if (nums[left] <= target && target <= nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (nums[mid] <= target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return false;
}
};
4、OJ153 find minimum in rotated sorted array
在旋转过的有序数组中(无重复元素),寻找最小值,如[4,5,6,1,2,3],需要找到最小值1
有OJ33的基础,很容易想到此题的思路:
1、旋转数组的最小值做哪?在右半部分的头
2、所以需要找到右半部分,逐渐消除掉左半部分查找区域
如发现mid的value大于最左,说明处于右半部分,接下来再[mid+1,right]查找
如已经落在右半部分,则right向左移,并不断判断更新最小值
OJ153代码:
class Solution {
public:
int findMin(vector<int>& nums) {
if (nums.empty()) {
return -1;
}
//最小值在数组右半部分的头, 所以必须设法找到右半部分, 并再找最小值
int left = 0, right = nums.size() - 1;
int min = INT_MAX;
while (left <= right) {
int mid = (left + right)/2;
if (min > nums[left]) {
min = nums[left];
}
if (nums[mid] >= nums[left]) {
left = mid + 1;
} else {
if (min > nums[mid]) {
min = nums[mid];
}
right = mid - 1;
}
}
return min;
}
};
5、OJ154 find minimum in rotated sorted array II
和OJ153区别是,数组内可能有重复元素。
有了OJ81的基础后,很清晰该怎么做:当发现mid和最左和最右的value都相同,则说明是[3,3,3,1,2,3,3,3,3,3,3,3,3,3,3,3]这样的情况,令最左和最右往相遇方向压缩
OJ154代码:
class Solution {
public:
int findMin(vector<int>& nums) {
if (nums.empty()) {
return -1;
}
int left = 0, right = nums.size() - 1, min = INT_MAX;
while (left <= right) {
int mid = (left + right)/2;
if (min > nums[left]) {
min = nums[left];
}
if (nums[mid] == nums[left] && nums[mid] == nums[right]) {
++left;
--right;
continue;
}
if (nums[mid] >= nums[left]) {
left = mid + 1;
} else {
if (min > nums[mid]) {
min = nums[mid];
}
right = mid - 1;
}
}
return min;
}
};
6、OJ88 merge sorted array
两个有序数组A和B,合并到A内,A有足够的空间容纳A和B的全部元素
本题考察的是:数组操作的时间复杂度掌握,如果是插入排序式的方式,B的每一个元素插入位置后的A的其他元素都需要移位,移位操作非常多,时间复杂度可近似认为是O(M * N);
在A空间足够的前提下,因为A与B均有序,则不断比较均为从后向前的A与B的元素大小,从A的尾部向头部方向的插入,时间复杂度降为O(M + N);
注意A或B任意一个为空的情况的处理;
OJ88代码:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
while (n) {
if (m > 0 && nums1[m - 1] > nums2[n - 1]) {
nums1[m + n - 1] = nums1[m - 1];
--m;
} else {
nums1[m + n - 1] = nums2[n - 1];
--n;
}
}
}
};
7、OJ4 median of two sorted arrays
求两个有序数组的中位数,或者衍生题:求两个有序数组的第K个元素;要求原地查找,O(N)时间复杂度;
注:本题不愧是hard,而且似乎表面看能搞一搞,但如没有见过没有准备,基本上没可能搞定;
所以,得记下思路和解法:
两个有序数组的第K个数(中位数,按OJ4的题意,如果两个数组元素和为奇数,则为两个数组统一排序后的中间那个元素,如为偶数,则为中间那两个元素的和除以2.0,所以求中位数相当于求中间的两个数组统一排序后的那一两个元素,属于求第K个数),有这样一个规律:
0、由A的前m个数,和B的前n个数,m+n = k,如果:
1、A[m - 1] == B[n - 1],即说明A和B的最小的K个数,最大值是A[m - 1]或B[n - 1](它们俩相等),这本身就已经是第K个数了
2、如果A[m - 1] < B[n - 1],说明A的前m个数都小于B[n - 1],而已知m + n = k,
这说明什么?说明A的前m个数不可能出现第K个数
应该怎么办?缩小包围圈,A去掉前m个数的部分;
3、如果A[m - 1] > B[n - 1],和2同理,说明B的前n个数不可能出现第K个数了,去掉这个范围
去掉了A或B的肯定不存在第K个数的范围后,A或B的剩余部分,加上对方上一轮的部分,继续执行第0步
两个问题点:
1、m和n怎么得来的?答案:按比例,比如A的元素个数为10,B为5,则m = K * (10/(10 + 5)),n = k - m,即A的前m个数,范围为[0, m - 1],B的范围为[0, n-1]
原因:目前描述不太透彻,待续
2、为什么对方的范围不变,还是上一轮的范围,如已知A[m - 1] < B[n - 1],为什么笃定第K个数肯定落在A[m, A.size() - 1]和B[0, n-1]两个区间以内?为什么不会出现在B[n, B.size() - 1]范围呢?
原因:已知m + n = K,已经扣除了A的m个,现在相当于求扣除A的m个后的两个数组的第K-m - 1 = n - 1个数;B的第n - 1后边的数都比B[n - 1]大,A的第m-1后边的都比A[m - 1]大,他们的个数和大于当前要求的第n - 1,所以第n - 1个数肯定存在于这两个部分内;这个一定思考清楚;
具体到代码,边界细节也很多;要考虑:
1、某一方数组已经空了;
2、只需要取剩余两数组的第1个即最小的;
3、另外,必须是数组长度大的数组,求idx = size(大)/(size(大) + size(小)),长度小的数组的idx = k - idx(大)
这个是上面的问题点1
OJ4代码:
class Solution {
public:
int helper (vector<int> v1, int sz1, vector<int> v2, int sz2, int kth) {
if (!sz1) {
return v2[kth - 1];
}
if (!sz2) {
return v1[kth - 1];
}
if (kth == 1) {
if (v1.empty()) {
return v2[0];
} else if (v2.empty()) {
return v1[0];
}
return (v1[0] < v2[0])?v1[0]:v2[0];
}
if (sz1 > sz2) {
return helper(v2, sz2, v1, sz1, kth);
}
int idx1 = (kth - 1) * sz1 / (sz1 + sz2), idx2 = kth - idx1 - 2;
if (v1[idx1] == v2[idx2]) {
return v1[idx1];
} else if (v1[idx1] < v2[idx2]) {
vector<int> newv1, newv2;
for (int i = idx1 + 1; i < sz1; i++) {
newv1.push_back(v1[i]);
}
for (int i = 0; i <= idx2; i++) {
newv2.push_back(v2[i]);
}
return helper(newv1, newv1.size(), newv2, newv2.size(), kth - idx1 - 1);
} else {
vector<int> newv1, newv2;
for (int i = 0; i <= idx1; i++) {
newv1.push_back(v1[i]);
}
for (int i = idx2 + 1; i < sz2; i++) {
newv2.push_back(v2[i]);
}
return helper(newv1, newv1.size(), newv2, newv2.size(), kth - idx2 - 1);
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if (nums1.empty() && nums2.empty()) {
return -1;
} else if (nums1.empty()) {
if (nums2.size() % 2) {
return nums2[nums2.size()/2];
} else {
return (nums2[nums2.size()/2 - 1] + nums2[nums2.size()/2])/2.0;
}
} else if (nums2.empty()) {
if (nums1.size() % 2) {
return nums1[nums1.size()/2];
} else {
return (nums1[nums1.size()/2 - 1] + nums1[nums1.size()/2])/2.0;
}
}
int m = nums1.size(), n = nums2.size();
if ((m + n) % 2) {
return helper(nums1, m, nums2, n, (m + n)/2 + 1);
} else {
int left = helper(nums1, m, nums2, n, (m + n)/2);
int right = helper(nums1, m, nums2, n, (m + n)/2 + 1);
return (left + right)/2.0;
}
}
};
8、OJ35 search insert position
在一个无重复有序数组中,如[1,2,3,4,5,],给定K,如K = 0或K=3或k=10,寻找k应该插入在哪个索引处,数组依然还是有序
本题考察二分查找运用
OJ35代码:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if (nums.empty()) {
return 0;
}
if (target <= nums[0]) {
return 0;
}
int st = 0, ed = nums.size() - 1;
while (st <= ed) {
int mid = (st + ed)/2;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] > target) {
ed = ed - 1;
} else {
st = st + 1;
}
}
return st;
}
};