题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
这是一个二维有序数组的查找问题,主要考察二分查找。但是也可以根据数组的特点解决。
思路一:二分查找,数组的每一行都是有序的,可以对每一行进行二分查找,时间复杂度O(MlogN),M是矩阵行数,N是矩阵列数,空间复杂度O(1)。
class Solution {
public:
int binary_search(vector<int>& v, int target)
{
//二分查找函数
int left = 0;
int right = v.size()-1;
while(left<=right){
int mid = left + (right - left)/2;
if(v.at(mid) == target)
return true;
else if(target > v.at(mid))
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
bool Find(int target, vector<vector<int> > array)
{
if(array.empty()) return false;
for(int i = 0;i<array.size();i++){
//这里可以优化下
//对于每一行最后一个数字,如果小于target,可以直接跳过
if(binary_search(array.at(i),target) != -1)
return true;
}
return false;
}
};
思路二:根据矩阵特点,对于左下角,往右走数字变大,往上走数字变小。对于右上角类似。
那么我们从左下角出发,target比当前值大,我们就往右走,target比当前值小,我们就往上走,这样一次可以淘汰一行或者一列,若target存在一定会找到。时间复杂度O(M+N),M是矩阵行数,N是矩阵列数,空间复杂度O(1)。
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int i = array.size()-1,j = 0;
while(i >= 0 && j < array[0].size())
{
if(array[i][j] == target)
return true;
else if(array[i][j] > target)
i--;
else
j++;
}
return false;
}
};
题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:这是一个对数组按规则排序的排序的算法。考虑双指针,类似于插入排序
一个指针从前往后寻找第一个偶数,另一个指针第一个指针往后寻找第一个奇数。由于要保持相对位置:存储次偶数的值,将两指针间的元素从后往前依次后移,将奇数移到第一个指针对应的位置,第一指针后移,此时第一指针前面所有元素都为奇数,完成一次操作。
class Solution {
public:
void reOrderArray(vector<int> &array) {
int len = array.size();
if(len <= 1){ // 数组空或长度为1
return;
}
int i = 0;
while(i < len){
int j = i + 1;
if(array[i]%2 == 0) // a[i]为偶数,j前进,直到替换
{
while(array[j]%2 == 0) // array[j]为偶数,前进
{// i为偶数,j也为偶数,一直后移到了末尾,证明后面都是偶数
if(j==len-1)
return;
j++;
}
// 此时array[j]为奇数
int count = j-i; //需要后移的元素数
int temp = array[i]; //保存偶数值,防止下一步被覆盖
array[i] = array[j]; //奇数前移
while(count>1) //这里不能使用count--
{
array[i+count] = array[i+count-1];//数组后移
count--;
}
//循环中array[i+1] = array[i],是奇数,temp的作用就是现在
array[i+1] = temp;
}
i++; //自增,不管array[i]是奇数还是偶数,都要进行下一步
}
}
};
题目描述:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:顺时针打印矩阵,注意结果是一维数组,首先确定矩阵的行树row以及列数col。打印过程中需要维护四个变量,即数组的上、下、左、右边界。执行四次循环,直到边界重合。
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
int row = matrix.size();
int col = matrix[0].size();
vector<int> result; //结果数组
if(row == 0||col == 0)
return result;
//数组的上、下、左、右边界
int left = 0,right = col - 1,top = 0,btm = row - 1;
while(left <= right&&top <= btm) //不使用 = 可能会漏掉某行或某列
{
for(int i = left;i <= right;i++)
result.push_back(matrix[top][i]);
if(top < btm) //避免重复打印
for(int i = top + 1;i <= btm;i++)
result.push_back(matrix[i][right]);
if(top < btm&&left < right)
for(int i = right - 1;i >= left;i--)
result.push_back(matrix[btm][i]);
if(top < btm&&left < right)
for(int i = btm - 1;i >= top + 1;i--)
result.push_back(matrix[i][left]);
left++;
right--;
top++;
btm--;
}
return result;
}
};
题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路一:采用阵地攻守的思想
第一个数字作为第一个士兵,守阵地;count = 1;遇到相同元素,count++;遇到不相同元素,即为敌人,同归于尽,count–;当遇到count为0的情况,又以新的i值作为守阵地的士兵,继续下去,到最后还留在阵地上的士兵,有可能是主元素。再加一次循环,记录这个士兵的个数看是否大于数组一般即可
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int tmp = numbers[0]; //储存第一个元素
int count = 1; //当前值的个数
for(int i = 1;i<numbers.size();i++){
if(numbers[i] == tmp){
count++; //相等,加一
}
else{
count--;
if(count == 0){
//同归于尽的情况
tmp = numbers[i];
count = 1;
}
}
}
count = 0;
for(int num:numbers){
if(num == tmp)
count++;
}
if(count > numbers.size()/2)
return tmp;
return 0;
}
};
思路二:暴力法
对于当前元素逐一比较,统计个数
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty())
return 0;
int tmp=0; //储存当前元素
for(int i = 0;i<numbers.size();i++)
{
tmp=numbers[i];
int times = 0; //每次需要归0
for(int j = 0;j<numbers.size();j++)
{
if(numbers[j] == tmp)
times++;
}
if(times>numbers.size()/2)
{
return tmp;
break; //这样的数只会存在一个
}
}
return 0;
}
};
题目描述:
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
思路一:暴力法
冒泡排序,输出前K个元素即可。
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(k>input.size())
return res;
else
{
for (int i = 0; i < input.size(); i++)
{
for (int j = 0; j < input.size() - i - 1; j++) //size-i-1是什么意思?表示后面i个已排好序
{
if (input[j] > input[j + 1])
{
int temp = input[j];
input[j] = input[j + 1];
input[j + 1] = temp;
}
}
}
for(int i=0;i<k;i++)
res.push_back(input[i]);
return res;
}
}
};
思路二:借助优先队列
使用priority_queue容器,默认从小到大排序,底层实现为最大堆
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(k > input.size()) return res;
priority_queue<int> q;
for (int a : input) {
q.push(a);
if (q.size() > k)
q.pop(); //弹出最大的元素
}
while (!q.empty()) {
res.push_back(q.top());
q.pop();
}
return res;
}
};
思路三:快速排序
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
// 快排
if(k > input.size()) return {};
quick_sort(input, 0, input.size() - 1, k);
return vector<int>(input.begin(), input.begin() + k);
}
void quick_sort(vector<int>& arr, int l, int r, int k) {
if (l >= r) return;
int x = arr[l], i = l - 1, j = r + 1;
while (i < j) {
while (arr[++ i] < x);
while (arr[-- j] > x);
if (i < j) swap(arr[i], arr[j]);
}
int s1 = j - l + 1;
if (s1 >= k) quick_sort(arr, l, j, k);
else quick_sort(arr, j + 1, r, k - s1);
}
};
题目描述:
例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和。
思路:动态规划
维护一个当前和current_sum与一个最大最数组和 max_line两个变量,若current_sum小于当前值array[i],则舍弃前面的值, current_sum=array[i],若current_sum>max_line,则max_line=current_sum。
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
if(array.empty())
return 0;
int current_sum=array[0];
int max_line=array[0];
for(int i = 1;i<array.size();i++){//动态规划
current_sum+=array[i];//从array[0]开始,遍历array[]
if(current_sum<array[i])//如果当前子序列之和加上下一个数后没有增加,则该段子序列
current_sum=array[i];//为一最大连续子序列
if(current_sum>max_line)//之后继续最大下一子序列的判断
max_line=current_sum;
}
return max_line;
}
};
题目描述:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路:排序
先把数字转换成字符串,对于所有可能的排列,我们尽量使得最高位越小,这样得到的结果越小。所以对字符数组进行排序。
class Solution {
public:
string PrintMinNumber(vector<int> numbers) {
string ret;
vector<string> numbers_str;
string t;
for(int i = 0; i < numbers.size(); ++i) //转成字符串存储
numbers_str.push_back(to_string(numbers[i]));
for(int i=0;i<numbers_str.size();i++){
//自己排序
for(int j=i+1;j<numbers_str.size();j++){
string a=(numbers_str[i]+numbers_str[j]);
string b=(numbers_str[j]+numbers_str[i]);
if(a>b){
t=numbers_str[i];
numbers_str[i]=numbers_str[j];
numbers_str[j]=t;
}
}
}
for(int i = 0; i < numbers_str.size(); ++i)
ret += numbers_str[i];
return ret;
}
};
借用lambda表达式
[](string& s1, string& s2){return s1 + s2 < s2 + s1;}
class Solution {
public:
string PrintMinNumber(vector<int> numbers) {
vector<string> strs;
string ans;
for(int i = 0; i < numbers.size(); i ++){
strs.push_back(to_string(numbers[i]));
}
//使用lambda表达式,借用sort()
sort(strs.begin(), strs.end(), [](string& s1, string& s2){return s1 + s2 < s2 + s1;});
for(int i = 0; i < strs.size(); i ++)
ans += strs[i];
return ans;
}
};
题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
思路:归并排序
(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
(c)把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对;
class Solution {
public:
int InversePairs(vector<int> data) {
int n = data.size();
vector<int> tmp(n);
return mergeSort(data, tmp, 0, n - 1)%1000000007;
}
long long mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
if (l >= r) {
return 0;
}
int mid = (l + r) / 2;
long long inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);
int i = l, j = mid + 1, pos = l;
while (i <= mid && j <= r) {
if (nums[i] <= nums[j]) {
tmp[pos] = nums[i];
++i;
inv_count += (j - (mid + 1));
}
else {
tmp[pos] = nums[j];
++j;
}
++pos;
}
for (int k = i; k <= mid; ++k) {
tmp[pos++] = nums[k];
inv_count += (j - (mid + 1));
}
for (int k = j; k <= r; ++k) {
tmp[pos++] = nums[k];
}
copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
return inv_count;
}
};
题目描述:
统计一个数字在排序数组中出现的次数。
思路一:一个排序数组,查找一个数出现的次数。第一个想法就是遍历整个数组,找到对应的数字,统计出现的次数。这当然可以,只是时间复杂度为O(N)。
思路二:我们使用二分查找,需要找到给定数字在数组中的左右边界。但需要注意,使用二分法查找边界时,对于边界收缩的条件需要仔细思考,如何收缩才能找到左/右边界?
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int n = data.size();
int left = 0,right = n - 1;
int left_index = 0,right_index = 0; //目标左右边界
while(left <= right){
//查找左边界
int mid = (left + right)/2;
if(data[mid] < k) left = mid + 1;
else right = mid - 1; // >= 时,右边界收缩
}
left_index = left; //记录左边界
left = 0,right = n - 1;
while(left <= right){
//右边界
int mid = (left + right)/2;
if(data[mid] <= k) left = mid + 1;// <= 时,左边界收缩
else right = mid - 1;
}
right_index = right;
if(left_index > right_index) return 0; //找不到的情况
return right_index - left_index + 1;
}
};
题目描述:
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路:拿到题目首先想到的方法肯定是暴力求解,使用两次循环,时间复杂度为O(N^2)。这里提供了一中使用位运算的解法。
异或运算的一个性质:一个数和0异或还是它本身,两个相同数字异或结果为0。当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对出现的都抵消了。
当有两个数出现时,异或的结果就是只出现一次的两个数的异或结果。如何将这两个数找出来?
我们只需找到异或结果的二进制任意一位不为零的位,比如:7:111
和 4:100
异或结果为 011
,最低位为1就说明两个数对应位不相等,即可使用flag = 001
即flag = 1
分别与数组元素与&
,及将数组分为两部分:最低位为1和为0的两部分,分别异或,每部分出现两次的数字变成了0,剩下的就是结果
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int len = data.size();
if(len < 2)
return;
int one = 0;
for(int i = 0;i<len;i++)
{
one = one^data[i]; //按位异或运算符^
}
//one是两个不同数的异或结果
int flag = 1;
while(flag)
{
if(one&flag)
break; //flag 相应位上值为1,即代表这两个不同的数这一位不同
flag = flag<<1;
}
for(int i = 0;i<len;i++)
{
if(flag&data[i]) //对应位为1的数异或结果为num1
*num1 = *num1^data[i];
else //对应位为0的数异或结果为num2
*num2 = *num2^data[i];
}
}
};
题目描述:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路:对于和相等的两个数字,两个数字越远,他们的积越小。采用双指针,一个从前往后,一个从后往前。
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> result;
int len = array.size();
if(len<=1)
return result;
int res_sum;
//从两端开始向里尝试,而且对于已排序数组,两端的数的乘积会小于中间部分的乘积
int i = 0;
int j = len - 1;
while(i<j)
{
res_sum = array[i]+array[j];
if(res_sum>sum)
j--;
else if(res_sum<sum)
i++;
else
{
result.push_back(array[i]);
result.push_back(array[j]);
break;
}
}
return result;
}
};
扩展:如果要求和最大的那个怎么办?
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> result;
int len = array.size();
if(len<=1)
return result;
int res_sum;
//从两端开始向里尝试,而且对于已排序数组,两端的数的乘积会小于中间部分的乘积
int i = 0;
int j = len - 1;
int max_res = INT_MIN;
int left_num = array[0],right_num = array[0];
while(i<j)
{
res_sum = array[i]+array[j];
if(res_sum>sum)
j--;
else if(res_sum<sum)
i++;
else
{
if(max_res >= array[i]*array[j]){
i++;
j--;
}
else{
max_res = array[i]*array[j];
left_num = array[i];
right_num = array[j];
}
}
}
if(right_num == array[0]) return result;
result.push_back(left_num);
result.push_back(right_num);
return result;
}
};
题目描述:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:范围0-n-1,建立一个数组,遍历所有数,遇到一个数在相应下标处加1,最后遍历数组,找到大于1的即可。
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers == NULL || length == 0)
return 0;
int counttable[256] = {0}; //int类型最大的数为255
for(int i = 0;i<length;i++)
{
counttable[numbers[i]]++;
}
int count = 0;
for(int i = 0;i<length;i++) //这里面不要搜索counttable数组
{
if(counttable[numbers[i]]>1){
duplication[count++] = numbers[i];
return true;
}
}
return false;
}
};
题目描述:
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
思路一:暴力法
class Solution {
public:
vector<int> multiply(const vector<int>& a) {
vector<int> res;
int len=a.size();
for(int i=0;i<len;i++){
int num=1;
for(int j=0;j<len;j++){
if(i==j)continue;
num*=a[j];
}
res.push_back(num);
}
return res;
}
};
思路二:具体如下所示,每个位置的结果相当于对应位置左面部分的乘积结果乘以右半部分的结果。
具体见代码:
class Solution {
public:
vector<int> multiply(const vector<int>& a) {
int length = a.size();
vector<int> res(length);
if(length != 0 ){
res[0] = 1;
//计算下三角连乘
for(int i = 1; i < length; i++){
res[i] = res[i-1] * a[i-1];
}
int temp = 1;
//计算上三角
for(int j = length-2; j >= 0; j--){
temp *= a[j+1];
res[j] *= temp;
}
}
return res;
}
};
题目描述:
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
思路:使用双向队列,用来保存有可能是滑动窗口最大值的数组的下标。
在存入一个数字的下标之前,首先要判断队列里已有数字是否小于待存入的数字。如果已有的数字小于待存入的数字,那么这些数字已经不可能是滑动窗口的最大值,因此需要在队列尾部删除(pop_back)。同时,如果队列头部的数字已经从窗口里滑出,那么滑出的数字也需要从队列的头部删除(pop_front)。
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res;
if(num.size()==0 || num.size()<size) return res;
//双端队列,从队头到队尾 依次存 窗口内最大元素的index ~ 最小元素的index
deque<int> window;
int right = 0;
while(right < num.size()){
//后续,窗口每右移一次,都会产生一个最大值[队列头位置的元素]
while(!window.empty() && num[right] > num[window.back()]){
//待入队元素比队尾元素大
window.pop_back();
}
window.push_back(right);
if(!window.empty() && right - window.front() >= size){ //队头不在窗口范围内
//必须吐槽一下牛客,right - window.front() >= size如果写成window.front() <= right - size就会报错是个什么原理,求解
window.pop_front();
}
if(right >= size - 1)
if(window.size())
res.push_back(num[window.front()]);
right++;
}
return res;
}
};
题目描述:
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:其实本题不能算是数组的范畴,应该属于回溯算法范围了。需要初始化一个二维标志数组(初始为0)记录路径是否有走过,走过就为1。
class Solution {
public:
int movingCount(int threshold, int rows, int cols)
{
vector<vector<int>> flag(rows,vector<int> (cols,0)); //记录是否走过的矩阵
return helper(0,0,rows,cols,flag,threshold);
}
private:
int helper(int i,int j,int rows,int cols,vector<vector<int>>& flag,int threshold)
{
//参数i,j表示起始坐标,flag记录是否走过的路径,threshold是限制
//越界判断
if(i < 0 || i >= rows || j < 0 || j >= cols || numSum(i) + numSum(j) > threshold || flag[i][j] == 1)
return 0;
flag[i][j] = 1;
//回溯法
return helper(i - 1, j, rows, cols, flag, threshold)
+ helper(i + 1, j, rows, cols, flag, threshold)
+ helper(i, j - 1, rows, cols, flag, threshold)
+ helper(i, j + 1, rows, cols, flag, threshold)
+ 1;
}
int numSum(int i)
{
int sum = 0;
while(i > 0){
sum += i%10;
i /= 10;
}
return sum;
}
};