问题4.1 寻找脚码
已知一个整数数组x[],其中的元素彼此都不同,而且也已经从小到大排列好。请用比较大小、相等的方式写一个程序,找出给定的数组中是否有一个元素满足x[i] = i的关系。举例而言,如果x[]有2, 1, 3, 7, 8, x[3] = 3, 因此3就是答案。
#include <iostream>
using namespace std;
int findTheIndex(int *array, int size)
{
if (array == NULL || size <= 0)
return -1;
int leftIndex = 1;
int rightIndex = size;
while (leftIndex <= rightIndex)
{
int midIndex = (rightIndex + leftIndex) / 2;
if (array[midIndex - 1] == midIndex)
return midIndex;
else if (array[midIndex - 1] > midIndex)
rightIndex = midIndex - 1;
else
leftIndex = midIndex + 1;
}
return -1;
}
void main()
{
int array[] = {0, 2, 4, 7, 8};
const int size = sizeof array / sizeof *array;
int index = findTheIndex(array, size);
if (index == -1)
cout << "can't find it" << endl;
else
cout << "index = " << index << endl;
}
问题4.2 寻找固定的和
有两个数组x[]与y[],各有m与n个元素,而且各个元素没有依顺序排列;d是一个已知的值,请写一个程序,看看在x[]和y[]中有没有满足x[i] + y[i] = d的元素。例如,若x[]为3, 7, 2, 4,y[]为1, 5, 2, 3,d为9;那么x[1] + y[2]与x[3] + y[1]都合乎条件,也就是9.
这道题的解题思路不难,如果遍历两个数组,复杂度为O(m * n),这个复杂度显然高了。如何降低复杂度呢?分析如下:
1. 先排序,然后二分查找。问题是对哪个数组排序,如果对m排序,则复杂度为O((m + n) * logm), 如果对n排序,则复杂度为O((m + n) * logn), 对比之下,也就是说对较长的数组排序,遍历较小的数组。
代码如下:
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
using namespace std;
struct Pair
{
Pair(int i = 0, int j = 0) : left(i), right(j) {}
int left;
int right;
};
int binarySearch(int *array, int size, int value)
{
if (array == NULL || size <= 0)
return -1;
int leftIndex = 0;
int rightIndex = size - 1;
while (leftIndex <= rightIndex)
{
int midIndex = (leftIndex + rightIndex) / 2;
if (array[midIndex] == value)
return midIndex;
if (array[midIndex] > value)
rightIndex = midIndex - 1;
else
leftIndex = midIndex + 1;
}
return -1;
}
int findSum(int *lhs, int sizeLhs, int *rhs, int sizeRhs, int sum, vector<Pair> &pvec)
{
if (lhs == NULL || sizeLhs <= 0 || rhs == NULL || sizeRhs == 0)
return -1;
sort(lhs, lhs + sizeLhs);
Pair pair;
for (int i = 0; i < sizeRhs; i++)
{
int diff = sum - rhs[i];
int index = binarySearch(lhs, sizeLhs, diff);
if (index == -1)
continue;
else
{
pvec.push_back(Pair(lhs[index], rhs[i]));
}
}
if (pvec.size() == 0)
return -1;
else
return 0;
}
void main()
{
vector<Pair> pvec;
int lhs[] = {3, 7, 2, 4};
int rhs[] = {1, 5, 2, 3};
const int sizeLhs = sizeof lhs / sizeof *lhs;
const int sizeRhs = sizeof rhs / sizeof *rhs;
int sum = 9;
int result = findSum(lhs, sizeLhs, rhs, sizeRhs, sum, pvec);
if (result != -1)
{
for (int i = 0; i < pvec.size(); i++)
{
cout << sum << " = " << pvec[i].left << " + " << pvec[i].right << endl;
}
}
else
{
cout << "not exist" << endl;
}
}
2. 将排序换成hash,复杂度立马降低为O(n)或者O(m)
考虑到标准库里没有hash map,而且hash函数实现起来虽然不难,但不是本题的重点~代码用map来替代
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <map>
using namespace std;
struct Pair
{
Pair(int i = 0, int j = 0) : left(i), right(j) {}
int left;
int right;
};
void constructHash(int *lhs, int size, map<int, int> &imap)
{
if (lhs == NULL || size <= 0)
return;
for (int i = 0; i < size; i++)
imap[lhs[i]] = 1;
}
int findSum(int *lhs, int sizeLhs, int *rhs, int sizeRhs, int sum, vector<Pair> &pvec)
{
if (lhs == NULL || sizeLhs <= 0 || rhs == NULL || sizeRhs == 0)
return -1;
map<int, int> imap;
constructHash(lhs, sizeLhs, imap);
Pair pair;
for (int i = 0; i < sizeRhs; i++)
{
int diff = sum - rhs[i];
map<int, int>::const_iterator iter = imap.find(diff);
if (iter == imap.end())
continue;
else
{
pvec.push_back(Pair(iter->first, rhs[i]));
}
}
if (pvec.size() == 0)
return -1;
else
return 0;
}
void main()
{
vector<Pair> pvec;
int lhs[] = {3, 7, 2, 4};
int rhs[] = {1, 5, 2, 3};
const int sizeLhs = sizeof lhs / sizeof *lhs;
const int sizeRhs = sizeof rhs / sizeof *rhs;
int sum = 9;
int result = findSum(lhs, sizeLhs, rhs, sizeRhs, sum, pvec);
if (result != -1)
{
for (int i = 0; i < pvec.size(); i++)
{
cout << sum << " = " << pvec[i].left << " + " << pvec[i].right << endl;
}
}
else
{
cout << "not exist" << endl;
}
}
问题4.3 无限式查找
已知一个数组,元素个数有多少并不是很清楚,但是数组元素已经依顺序从小到大排好,而且在数组最后添加了足够多的无穷记号,无穷表示最大的值,比数组中每一个元素都大,而且个数足够多。试编写一个程序,在这个数组中找出某个给定的值。
思路:每次查找1、2、4、8...个数组元素
x[0]
x[1], x[2]
x[3], x[4], x[5], x[6]
x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14]
如果rightIndex大于size - 1, 则rightindex 取 size - 1即可
#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <map>
using namespace std;
#define INF 65535
int array[] = {1, 3, 4, 9, 10, 17, 20, 24, 28, 31, 36, 42, 43, 45, 50, 53, 55, 57, 59, INF, INF, INF, INF, INF, INF, INF, INF, INF};
const int size = sizeof array / sizeof *array;
int binarySearch(int *array, int left, int right, int dest)
{
if (array == NULL || left < 0 || right < 0 || left > right)
return -1;
while (left <= right)
{
int mid = (left + right) / 2;
if (array[mid] == dest)
return mid;
else if (array[mid] > dest)
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
int findDestValue(int *array, int size, int dest)
{
if (array == NULL || size <= 0)
return -1;
int leftIndex = 0;
int rightIndex = 0;
int dist = 1;
while (leftIndex < size)
{
int result = binarySearch(array, leftIndex, rightIndex, dest);
if (result == -1)
{
leftIndex = rightIndex + 1;
dist *= 2;
rightIndex = rightIndex + dist;
if (rightIndex >= size - 1)
rightIndex = size - 1;
}
else
return result;
}
return -1;
}
void main()
{
for (int i = 0; i < size; i++)
{
if (array[i] != INF)
{
int result = findDestValue(array, size, array[i]);
if (result == -1)
cout << "can't find the value" << endl;
else
cout << "result = " << result + 1 << endl;
}
}
}
问题4.4 寻找极小值
说一个组数是以循环顺序排列的,这就是说在数组中有个元素i, 自xi起有这样的关系x0 < x1 < x2 < ... < xi-1, xi < xi - 1 < .. < xn < x0;比如说,8, 10, 14, 15, 2, 6这6个元素就是循环排列的,因为自2起递增,到了最后一个元素就转折为第一个元素,再依顺序递增。换句话说,如果把xi, xi+ 1, ..., xn取出,并且接到数组开头,就是一个从小到大的排列顺序(这就是个旋转的动作)。请写一个程序,接受一个以循环顺序排列的数组,把它的极小值找出来,以上面数据为例,程序应该输出2.
思路:每次取midIndex的value时,和最右边的值相比,如果比右边的值大,则leftIndex = midIndex + 1,否则rightIndex = midIndex;
#include <iostream>
using namespace std;
int cyclicMin(int *array, int size)
{
if (array == NULL || size <= 0)
return -1;
int leftIndex = 0;
int rightIndex = size - 1;
int midIndex;
while (leftIndex < rightIndex)
{
midIndex = (leftIndex + rightIndex) / 2;
if (array[midIndex] < array[rightIndex])
rightIndex = midIndex;
else
leftIndex = midIndex + 1;
}
return leftIndex;
}
void main()
{
int array[] = {8, 10, 14, 15, 2, 6};
const int size = sizeof array / sizeof *array;
int result = cyclicMin(array, size);
cout << "result = " << array[result] << endl;
}
问题4.5 两个数组的中位数
已知两个数组x[] 和 y[],各有n个元素,并且已经以从小到大的顺序排好,请写个程序,用少于n次比较找出x[]和y[]合并后的中位数
思路:x[]和y[]的元素个数相同~每次取x[mid]和y[mid],比较二者大小,如果x[mid]比y[mid]小,则去掉x[0]~x[mid - 1]和y[mid + 1]和y[sizeY - 1]之间的元素。
对于n > 2的情形,midIndex肯定不会是0,所以其左右都有元素,那么比较两数组中位数,肯定会有元素可以用来删除
对于n <= 2,则需要特殊考虑:
1. 如果n = 1, 则直接判断即可,取较小的那个值
2. 如果n = 2,则两个数组较小的元素对比、较大的元素对比,去掉最小的元素和最大的元素,然后比较剩下的两个元素,取较小的值即可
代码不难写,如下:
#include <iostream>
using namespace std;
int array1[] = {1, 3, 5, 7, 9};
int array2[] = {2, 4, 6, 8, 10};
const int size = sizeof array1 / sizeof *array1;
int findMidValue(int *array1, int *array2, int size)
{
if (array1 == NULL || array2 == NULL || size <= 0)
return -1;
if (size == 1)
return min(array1[0], array2[0]);
if (size == 2)
{
int candidate1 = max(array1[0], array2[0]);
int candidate2 = min(array1[1], array2[1]);
return min(candidate1, candidate2);
}
int leftIndex1 = 0, rightIndex1 = size - 1;
int leftIndex2 = 0, rightIndex2 = size - 1;
while (leftIndex1 != rightIndex1 - 1 && leftIndex2 != rightIndex2 - 1)
{
int midIndex1 = (leftIndex1 + rightIndex1) / 2;
int midIndex2 = (leftIndex2 + rightIndex2) / 2;
if (array1[midIndex1] < array2[midIndex2])
{
leftIndex1 = midIndex1;
rightIndex2 = midIndex2;
}
else if (array1[midIndex1] > array2[midIndex2])
{
rightIndex1 = midIndex1;
leftIndex2 = midIndex2;
}
else
{
return array1[midIndex1];
}
}
int candidate1 = max(array1[leftIndex1], array2[leftIndex2]);
int candidate2 = min(array1[rightIndex1], array2[rightIndex2]);
return min(candidate1, candidate2);
}
void main()
{
int midValue = findMidValue(array1, array2, size);
cout << "midValue = " << midValue << endl;
}
问题4.6 寻找中间值
已知一个整数数组x[]有n个元素。对于任意两个相邻元素x[i]与x[i + 1]而言,他们的差的绝对值不会超过1,用数学式来写就是|x[i] - x[i+1]| <= 1,而且x[0] < x[n-1]。请写一个程序,在x[]数组中找出它是否有某一个输入的值a,x[0]<=a<=x[n-1],程序一共做了多少次比较?能够做到少于n次吗?
没能理解这道题的意思。。。
问题4.7 3个数组的共同元素
有3个数组x[], y[]与z[],各有x、y与z个元素,而且三者都已经从小到大依序排列。请写一个程序,找出值最小的共同元素(也就是同时在3个数组中出现,并且值最小的元素),若没有共同元素,请显示合适信息。
思路:其实这道题很简单,而且题目也未曾规定不允许判断相等。。。
#include <iostream>
using namespace std;
int array1[] = {1, 3, 6, 7, 8};
const int size1 = sizeof array1 / sizeof *array1;
int array2[] = {2, 3, 6, 10, 12};
const int size2 = sizeof array2 / sizeof *array2;
int array3[] = {3, 5, 6, 7, 9};
const int size3 = sizeof array3 / sizeof *array3;
int findCommonValue(int *array1, int *array2, int *array3, int size1, int size2, int size3,
int &position1, int &position2, int &position3)
{
if (array1 == NULL || array2 == NULL || array3 == NULL ||
size1 <= 0 || size2 <= 0 || size3 <= 0)
return -1;
position1 = 0;
position2 = 0;
position3 = 0;
while (position1 < size1 && position2 < size2 && position3 < size3)
{
if (array1[position1] < array2[position2])
position1++;
else if (array2[position2] < array3[position3])
position2++;
else if (array3[position3] < array1[position1])
position3++;
else
break;
}
if (position1 == size1 || position2 == size2 || position3 == size3)
return -1;
else
return 0;
}
void main()
{
int position1;
int position2;
int position3;
int result = findCommonValue(array1, array2, array3, size1, size2, size3, position1, position2, position3);
if (result == -1)
cout << "can't find it" << endl;
else
{
cout << "position1 = " << position1 << endl;
cout << "position2 = " << position2 << endl;
cout << "position3 = " << position3 << endl;
}
}
很容易想到的一个解法如下:
#include <iostream>
using namespace std;
int array1[] = {1, 3, 6, 7, 8};
const int size1 = sizeof array1 / sizeof *array1;
int array2[] = {2, 3, 6, 10, 12};
const int size2 = sizeof array2 / sizeof *array2;
int array3[] = {3, 5, 6, 7, 9};
const int size3 = sizeof array3 / sizeof *array3;
int findCommonValue(int *array1, int *array2, int *array3, int size1, int size2, int size3,
int &position1, int &position2, int &position3)
{
if (array1 == NULL || array2 == NULL || array3 == NULL ||
size1 <= 0 || size2 <= 0 || size3 <= 0)
return -1;
position1 = 0;
position2 = 0;
position3 = 0;
while (position1 < size1 && position2 < size2 && position3 < size3)
{
while (array1[position1] != array2[position2])
{
position1++;
position2++;
}
while (array3[position3] <= array2[position2])
{
if (array3[position3] != array2[position2])
position3++;
else
return 0;
}
position1++;
position2++;
}
if (position1 == size1 || position2 == size2 || position3 == size3)
return -1;
else
return 0;
}
void main()
{
int position1;
int position2;
int position3;
int result = findCommonValue(array1, array2, array3, size1, size2, size3, position1, position2, position3);
if (result == -1)
cout << "can't find it" << endl;
else
{
cout << "position1 = " << position1 << endl;
cout << "position2 = " << position2 << endl;
cout << "position3 = " << position3 << endl;
}
}
仔细对比两个解法,oh, god, 下面的复杂度居然是O(min(n1, n2) * n3),而第一个解法的复杂度为O(min(n1, n2, n3))。。。
把while换成if,结果复杂度立马降下来了,坑你妹。。。
int findCommonValue(int *array1, int *array2, int *array3, int size1, int size2, int size3,
int &position1, int &position2, int &position3)
{
if (array1 == NULL || array2 == NULL || array3 == NULL ||
size1 <= 0 || size2 <= 0 || size3 <= 0)
return -1;
position1 = 0;
position2 = 0;
position3 = 0;
while (position1 < size1 && position2 < size2 && position3 < size3)
{
if (array1[position1] != array2[position2])
{
position1++;
position2++;
}
if (array3[position3] <= array2[position2])
{
if (array3[position3] != array2[position2])
position3++;
else
return 0;
}
position1++;
position2++;
}
if (position1 == size1 || position2 == size2 || position3 == size3)
return -1;
else
return 0;
}
问题4.8 寻找最小与次小元素
请写一个函数,接收一个数组,找出该数组中最小与次小的元素的值
思路:
1. 两个变量small1 和 small2,分别用来保存最小值和次小值,取第三个及之后的元素和这两个值来判断,更新这两个变量
2. 分治法,每次分成两半,分别return当前的最小值和次小值
3. 一次拿两个元素,比较出最大值和最小值,然后再和small1和small2比较
思路1代码:
#include <iostream>
using namespace std;
int array[] = {8, 7, 2, 4, 5, 1, 3, 10};
const int size = sizeof array / sizeof *array;
int findTwoMins(int *array, int size, int &firstMin, int &secondMin)
{
if (array == NULL || size <= 0)
return -1;
if (size == 1)
{
firstMin = array[0];
secondMin = array[0];
return 0;
}
if (array[0] > array[1])
{
firstMin = array[1];
secondMin = array[0];
}
else
{
firstMin = array[0];
secondMin = array[1];
}
int index = 2;
while (index < size)
{
if (array[index] < firstMin)
{
secondMin = firstMin;
firstMin = array[index];
}
else if (array[index] < secondMin)
{
secondMin = array[index];
}
index++;
}
return 0;
}
void main()
{
int firstMin;
int secondMin;
int result = findTwoMins(array, size, firstMin, secondMin);
cout << "firstMin = " << firstMin << endl;
cout << "secondMin = " << secondMin << endl;
}
仔细分析上面代码的比较次数,不难发现需要用到2n - 3次,显然这个比较次数太大了。。。
再来看思路2的代码:
#include <iostream>
using namespace std;
int array[] = {8, 7, 2, 4, 5, 1, 3, 10};
const int size = sizeof array / sizeof *array;
int findTwoMins(int *array, int leftIndex, int rightIndex, int &firstMin, int &secondMin)
{
if (array == NULL || leftIndex < 0 || rightIndex < 0 || leftIndex > rightIndex)
return -1;
if (leftIndex == rightIndex)
{
firstMin = array[leftIndex];
secondMin = array[rightIndex];
return 0;
}
if (leftIndex == rightIndex - 1)
{
if (array[leftIndex] > array[rightIndex])
{
firstMin = array[rightIndex];
secondMin = array[leftIndex];
}
else
{
firstMin = array[leftIndex];
secondMin = array[rightIndex];
}
return 0;
}
int midIndex = (leftIndex + rightIndex) / 2;
int firstMinLeft, secondMinLeft;
int firstMinRight, secondMinRight;
findTwoMins(array, leftIndex, midIndex, firstMinLeft, secondMinLeft);
findTwoMins(array, midIndex + 1, rightIndex, firstMinRight, secondMinRight);
if (firstMinLeft < firstMinRight)
{
firstMin = firstMinLeft;
secondMin = min(secondMinLeft, firstMinRight);
}
else
{
firstMin = firstMinRight;
secondMin = min(firstMinLeft, secondMinRight);
}
return 0;
}
void main()
{
int firstMin;
int secondMin;
int result = findTwoMins(array, 0, size - 1, firstMin, secondMin);
cout << "firstMin = " << firstMin << endl;
cout << "secondMin = " << secondMin << endl;
}
上面的比较次数为1.5n - 2
思路3的代码就不写了,比较次数是1.5n - 2
对比三种思路,思路2的比较次数虽然和3相同,但是递归调用的时间开销显然比循环大,所以思路3才是最合适的解法~~
问题4.9 查找矩阵
已知有n列n行的矩阵M,它的元素满足一个很特殊的性质,即对于任一元素Mi,j而言,都会小于在它右边与下方的元素(如果存在的话),换句话说,Mi,j < Mi,j+1,而且Mi,j < Mi+1,j,现在又有一个值K,请写一个程序,检查矩阵M中是否存在K。
这道题在那本剑指offer啥书上的前几题,比较简单,不说了~
问题4.10 表示成两个数平方和
已知一个正整数R,请写一个程序,找出所有满足x^2 + y^2 = R的正整数对X与Y。
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
struct Pair
{
Pair(int i = 0, int j = 0) : x(i), y(j) {}
int x;
int y;
};
int findSquareSum(int number, vector<Pair> &pvec)
{
if (number <= 1)
return -1;
int left = 1;
int right = sqrt(double(number)) + 0.5;
while (left <= right)
{
if (left * left + right * right == number)
{
pvec.push_back(Pair(left, right));
left++;
right--;
}
else if (left * left + right * right < number)
left++;
else
right--;
}
if (pvec.size() == 0)
return - 1;
else
return 0;
}
int i;
void main()
{
vector<Pair> pvec;
for (i = 2; i <= 100; i++)
{
int result = findSquareSum(i, pvec);
if (result == 0)
{
for (int i = 0; i < pvec.size(); i++)
cout << ::i << " = " << pvec[i].x << "*" << pvec[i].x << " + " << pvec[i].y << "*" << pvec[i].y << endl;
}
pvec.clear();
}
}
问题4.11 最大方块区域
在一个矩阵中,相连在一起的一块正方形区域就叫做一个子区域,如M3,5、M3,6、M3,7、M4,5、M4,6、M4,7、M5,5、M5,6与M5,7就是矩阵M中从M3,5起的一个3*3的子区域(subblock)。请写一个程序,接受一个方阵(列数与行数相同),并且再接收一个已知的值,令其为K,请找出在给定方阵中值全部是K的最大一个子区域,并且报告出这个子区域在矩阵中的位置与它的大小。