剑指——数组
题号根据牛客网上顺序编排
JZ1——二维数组的查找
知识点:数组、二分法
题目描述:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[ [1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15] ]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
示例:
输入:7, [ [1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15] ]
返回值:true
说明:存在7,返回true
- 解法1:采用遍历算法,暴力破解
class Solution{
public:
bool Find(int target, vector<vector<int> > array){
if(array.size() == 0 || array[0].size() == 0)
return false;
for(int i=0; i<array.size(); i++)
{
for(int j=0; j<array[0].size(); j++)
{
if(array[i][j] == target)
return true;
}
}
return false;
}
}
********************** 官方遍历 **********************
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
// 判断数组是否为空
if (array.size() ==0 || array[0].size() ==0) return false;
for (const auto& vec : array) {
for (const int val : vec) {
if (val == target)
return true;
}
}
return false;
}
};
时间复杂度:O(n*m), 空间复杂度:O(1)
- 解法2:二分法(初始点在右上角或者左下角)
//这里要注意不能简单的代入一维数组的二分思路
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size() == 0 || array[0].size() == 0)
return false;
int row = 0;
int col = array[0].size()-1;
while(row<array.size() && col>=0){
if(target>array[row][col]){
row++;
}
else if(target < array[row][col]){
col--;
}
else
return true;
}
return false;
}
};
时间复杂度:O(m+n),空间复杂度:O(1)
JZ4——重建二叉树
知识点:二叉树、数组
题目描述:给定某二叉树的前序遍历和中序遍历,请重建出该二叉树并返回它的头结点。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
提示:
1.0 <= pre.length <= 2000
2.vin.length == pre.length
3.0 <= pre[i], vin[i] <= 10000
4.pre 和 vin 均无重复元素
5.vin出现的元素均出现在 pre里
6.只需要返回根结点,系统会自动输出整颗树做答案对比
输入:[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
返回值:{1,2,3,4,#,5,6,#,7,#,#,8}
说明:返回根节点,系统会输出整颗二叉树对比结果
//解答此题,首先必须要了解二叉树的前序遍历、中序遍历、后序遍历,还有递归算法
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution{
public:
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin){
if(pre.size() == 0 || vin.size() == 0){
return NULL;
}
TreeNode *root = new TreeNode(pre[0]);
vector<int> preleft, preright, vinleft, vinright;
int gen = 0;
for(int i=0; i<pre.size(); i++){
if(vin[i] == pre[0]){
gen = i;
break;
}
}
for(int = i=0; i<gen; i++){
preleft.push_back(pre[i+1]);
vinleft.push_back(vin[i]);
}
for(int i=gen+1; i<pre.size(); i++){
preright.push_back(pre[i]);
vinright.push_back(vin[i]);
}
root->left = reConstructBinaryTree(preleft, vinleft);
root->right = reConstructBinaryTree(preright, vinrighy);
return root;
}
}
******************** 官方重建 ********************
class Solution {
public:
TreeNode* rebuild(vector<int>& pre, int pre_left, int pre_right, vector<int>& vin, int vin_left, int vin_right) {
if (pre_left > pre_right||vin_left > vin_right) return nullptr;
TreeNode* root = new TreeNode(pre[pre_left]);
for (int i=vin_left; i<=vin_right; ++i) {
if (vin[i] == root->val) {
root->left = rebuild(pre, pre_left+1, pre_left+i-vin_left, vin, vin_left, i-1);
root->right = rebuild(pre, pre_left+i-vin_left+1, pre_right, vin, i+1, vin_right);
break;
}
}
return root;
}
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
return rebuild(pre, 0, pre.size()-1, vin, 0, vin.size()-1);
}
};
JZ7——斐波那契数列
知识点:数组、递归
题目描述:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n≤39
示例:
输入:4
返回值:3
- 解法1:递归
class Solution {
public:
int Fibonacci(int n) {
if(n==0||n==1)
return n;
return Fibonacci(n-1) + Fibonacci(n-2);
}
};
时间复杂度:O(2^n),空间复杂度:递归栈的空间
- 解法2:记忆化搜索
class Solution {
public:
int Fib(int n, vector<int>& dp) {
if (n==0 || n==1) return n;
if (dp[n] != -1) return dp[n];
return dp[n] = Fib(n-1,dp) + Fib(n-2,dp);
}
int Fibonacci(int n) {
vector<int> dp(45, -1); // 因为答案都是>=0 的, 所以初始为-1,表示没计算过
return Fib(n, dp);
}
};
时间复杂度:O(n),空间复杂度:O(N)+递归栈的空间
- 解法3:动态规划
class Solution {
public:
int Fibonacci(int n) {
vector<int> dp(n+1, 0);
dp[1] = 1;
for (int i=2; i<=n; ++i) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
时间复杂度:O(n),空间复杂度:O(N)
- 解法4
class Solution {
public:
int Fibonacci(int n) {
if (n == 0 || n == 1) return n;
int a = 0, b = 1, c;
for (int i=2; i<=n; ++i) {
c = a + b;
a = b;
b = c;
}
return c;
}
};
时间复杂度:O(n),空间复杂度:O(1)
JZ13——调整数组顺序使奇数位于偶数前面
题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
示例:
输入:[1,2,3,4]
返回值:[1,3,2,4]
- 解法:遍历
思路:创建奇数数组和偶数数组,再遍历原数组,碰到奇数放入奇数数组,碰到偶数放到偶数数组,再将偶数数组依次插入奇数组。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* @param array int整型vector
* @return int整型vector
*/
vector<int> reOrderArray(vector<int>& array) {
// write code here
vector<int> odd_num, even_num;
int i=0;
while(i<array.size()) {
if(array[i]%2==1) {
odd_num.push_back(array[i]);
i++;
}
else{
even_num.push_back(array[i]);
i++;
}
}
int m=0;
while(m<even_num.size()){
odd_num.push_back(even_num[m++]);
}
return odd_num;
}
};
******************* 网友遍历 ********************
class Solution {
public:
vector<int> reOrderArray(vector<int>& array) {
// write code here
if(array.empty())
return array;
vector<int> ret(array.size());
int num=0;
for(int i=0;i<array.size();i++) {
if(array[i]%2) {
ret[num++]=array[i];
}
}
for(int j=0;j<array.size();j++){
if(array[j]%2==0) {
ret[num++]=array[j];
}
}
return ret;
}
};
时间复杂度:O(n),空间复杂度:O(n)
JZ9——顺时针打印矩阵
题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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]
示例:
输入:[ [1,2], [3,4] ]
返回值:[1,2,4,3]
- 解法1:旋转打印
*********** 官方解法 ************
class Solution {
public:
void print(int lx, int ly, int rx, int ry, vector<vector<int>> &matrix, vector<int> &ret) {
for (int j=ly; j<=ry; ++j)
ret.push_back(matrix[lx][j]);
for (int i=lx+1; i<=rx; ++i)
ret.push_back(matrix[i][ry]);
int h = rx - lx + 1;
if (h > 1){
for (int rj=ry-1; rj>=ly; --rj)
ret.push_back(matrix[rx][rj]);
}
int w = ry - ly + 1;
if (w > 1){
for (int ri = rx-1; ri>=lx+1; --ri)
ret.push_back(matrix[ri][ly]);
}
}
vector<int> printMatrix(vector<vector<int>>& matrix) {
vector<int> ret;
if (matrix.empty()) return ret;
int lx = 0, ly = 0;
int rx = matrix.size()-1, ry = matrix[0].size()-1;
while (lx <= rx && ly <= ry) {
print(lx++, ly++, rx--, ry--, matrix, ret);
}
return ret;
}
};
************** 网友解法 *****************
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
vector<int> q;
int row = matrix.size(), col = matrix[0].size();
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int x=0, y=0, d = 1;
for(int i=0; i < row * col; i++){
q.push_back(matrix[x][y]);
//走过的路径置为0
matrix[x][y] = 0;
int a = x + dx[d], b = y + dy[d];
if(a<0 || a>=row || b<0 || b>=col || matrix[a][b]==0){
d = (d+1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return q;
}
};
JZ28——数组中出现次数超过一半的数字
知识点:哈希、数组
题目描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。你可以假设数组是非空的,并且给定的数组总是存在多数元素。1<=数组长度<=50000
示例:
输入:[1,2,3,2,2,2,5,4,2]
返回值:2
- 解法1:哈希法
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
unordered_map<int,int> mp;
for (const int val : numbers) ++mp[val];
for (const int val : numbers) {
if (mp[val] > numbers.size() / 2 ) return val;
}
return 0;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
- 解法2:排序法
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
sort(numbers.begin(), numbers.end());
int cond = numbers[numbers.size() / 2];
int cnt = 0;
for (const int k :numbers) {
if (cond == k) ++cnt;
}
if (cnt > numbers.size() / 2) return cond;
return 0;
}
};
时间复杂度:O(nlongn)
空间复杂度:O(1)
**********************************************************
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
sort(numbers.begin(), numbers.end());
return numbers[numbers.size()/2];
}
};
- 解法3:候选法(最优解)
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
int cond = -1;
int cnt = 0;
for (int i=0; i<numbers.size(); ++i) {
if (cnt == 0) {
cond = numbers[i];
++cnt;
}
else {
if (cond == numbers[i]) ++cnt;
else --cnt;
}
}
cnt = 0;
for (const int k :numbers) {
if (cond == k) ++cnt;
}
if (cnt > numbers.size() / 2) return cond;
return 0;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
JZ35——数组中的逆序对
题目描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
对于%50的数据,size≤10 ^4
对于%100的数据,size≤10 ^5
输入描述:题目保证输入的数组中没有的相同的数字
示例1:
输入:[1,2,3,4,5,6,7,0]
返回值:7
- 解法1:暴力求解
class Solution {
private:
const int kmod = 1000000007;
public:
int InversePairs(vector<int> data) {
int ret = 0;
int n = data.size();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (data[i] > data[j]) {
ret += 1;
ret %= kmod;
}
}
}
return ret;
}
};
对于10^5数据,O(N^2)算法显然超时。
时间复杂度:O(N^2)
空间复杂度:O(1)
- 解法2:归并排序
class Solution {
public:
int InversePairs(vector<int> data) {
if(data.size() <= 1) return 0;
vector<int> copy(data);
int res = InversePairsScore(data, copy, 0,data.size() - 1);
return res;
}
int InversePairsScore(vector<int>&data,vector<int>©,int begin,int end){
if(begin >= end) return 0;
int middle = begin + (end -begin)/2;
int low1 = begin,high1 = middle,low2 = middle + 1,high2 = end;
int left = InversePairsScore(copy, data, low1, high1);
int right = InversePairsScore(copy, data, low2, high2);
int res = 0,copyindex = low1;
while(low1<= high1 && low2<=high2){
if(data[low1] > data[low2]){
copy[copyindex++] = data[low2++];
res += high1 - low1 + 1;
res %= 1000000007;
}
else{
copy[copyindex++] = data[low1++];
}
}
while(low1<=high1){
copy[copyindex++] = data[low1++];
}
while(low2<=high2){
copy[copyindex++] = data[low2++];
}
return (res+left+right)%1000000007;
}
};
时间复杂度:O(NlogN)
空间复杂度:O(N)
JZ37——数字在升序数组中出现的次数
知识点:数组、二分
题目描述:统计一个数字在升序数组中出现的次数。
示例:
输入:[1,2,3,3,3,3,4,5],3
返回值:4
- 解法1:暴力求解
class Solution {
public:
int GetNumberOfK(vector<int> nums ,int target) {
int ret = 0;
for (int val : nums) {
if (val == target)
++ret;
}
return ret;
}
};
时间复杂度:O(N)
空间复杂度:O(1)
- 解法2:二分查找
class Solution {
public:
int GetNumberOfK(vector<int> nums ,int target) {
int lbound = 0, rbound = 0;
// 寻找上界
int l = 0, r = nums.size();
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] < target) {
l = mid + 1;
}
else {
r = mid;
}
}
lbound = l;
// 寻找下界
l = 0, r = nums.size();
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] <= target) {
l = mid + 1;
}
else {
r = mid;
}
}
rbound = l;
return rbound - lbound;
}
};
时间复杂度:O(logN)
空间复杂度:O(1)
JZ42——和为S的两个数字
知识点:数组、双指针
题目描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回两个数的乘积最小的,如果无法找出这样的数字,返回一个空数组即可。
返回值描述:对应每个测试案例,输出两个数,小的先输出。
示例:
输入:[1,2,4,7,11,15],15
返回值:[4,11]
- 解法1:暴力求解
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
if(array.empty()) return vector<int> ();
vector<int> ans;
int i=0, temp=0;
while(temp==0)
{
if((array[i]<=sum/2) && (array[i+1]>=sum/2))
temp = i;
i++;
}
int first = temp;
int last = temp+1;
while(first>=0 && last<array.size())
{
if((array[first]+array[last]) > sum)
--first;
else if((array[first]+array[last]) < sum)
last++;
else
{
ans.push_back(array[first]);
ans.push_back(array[last]);
first--;
}
}
if(first<-1 || last>array.size() || ans.empty())
return {};
if(ans.size()==2)
return ans;
else
{
vector<int> mul;
for(int i=0;i<ans.size()/2;i++)
mul.push_back(ans[2*i]*ans[2*i+1]);
int cnt=0;
int num=mul[0];
for(int j=1;j<mul.size();j++)
{
if(num>mul[j])
{
num=mul[j];
cnt=j;
}
}
vector<int> comp;
comp.push_back(ans[2*cnt]);
comp.push_back(ans[2*cnt+1]);
return comp;
}
}
};
- 解法2:双指针
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
if (array.empty()) return vector<int>();
int tmp = INT_MAX;
pair<int, int> ret;
int i = 0, j = array.size();
while (i < j) {
if (array[i] + array[j-1] == sum) {
if (array[i]*array[j-1] < tmp) {
tmp = array[i] * array[j-1];
ret = {i, j-1};
}
++i, --j;
}
else if (array[i] + array[j-1] < sum) {
++i;
}
else {
--j;
}
}
if (ret.first == ret.second) return vector<int>();
return vector<int>({array[ret.first], array[ret.second]});
}
};
时间复杂度:O(n)
空间复杂度:O(1)
- 解法3:哈希表
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
if (array.empty()) return vector<int>();
int tmp = INT_MAX;
pair<int, int> ret;
unordered_map<int,int> mp;
for (int i=0; i<array.size(); ++i) {
mp[array[i]] = i;
}
for (int i=0; i<array.size(); ++i) {
if (mp.find(sum-array[i]) != mp.end()) {
int j = mp[sum-array[i]];
if ( j > i && array[i]*array[j] < tmp) {
tmp = array[i] * array[j];
ret = {i, j};
}
}
}
if (ret.first == ret.second) return vector<int>();
return vector<int>({array[ret.first], array[ret.second]});
}
};
时间复杂度:O(n)
空间复杂度:O(n)
JZ50——数组中重复的数字
题目描述:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任一一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1
示例:
输入:[2,3,1,0,2,5,3]
返回值:2
说明:2或3都是对的
- 解法1:遍历
class Solution {
public:
int duplicate(vector<int>& numbers) {
// write code here
int len = numbers.size();
for(int i=0;i<len-1;i++)
{
for(int j=i+1; j<len; j++)
{
if(numbers[i]==numbers[j])
return numbers[i];
}
}
return -1;
}
};
- 解法2:
public static int duplicate (int[] numbers) {
// write code here
int[] countarray=new int[numbers.length];
for(int i=0;i<numbers.length;i++) {
countarray[numbers[i]]++;
if(countarray[numbers[i]]>1) {
return numbers[i];
}
}
return -1;
}
- 解法3:map查字典
class Solution {
public:
int duplicate(vector<int>& numbers) {
// write code here
map<int,int> m;
for(int i=0;i<numbers.size();i++)
{
m[numbers[i]]++;
if(m[numbers[i]]>1)return numbers[i];
}
return -1;
}
};
JZ51——构建乘积数组
题目描述:
给定一个数组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];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。
示例:
输入:[1,2,3,4,5]
返回值:[120,60,40,30,24]
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
vector<int> B(A.size(), 1);
for (int i=1; i<A.size(); ++i) {
B[i] = B[i-1] * A[i-1]; // left[i]用B[i]代替
}
int tmp = 1;
for (int j=A.size()-2; j>=0; --j) {
tmp *= A[j+1]; // right[i]用tmp代替
B[j] *= tmp;
}
return B;
}
};
时间复杂度:O(N)
空间复杂度: O(1)
JZ32——把数组排成最小的数
题目描述:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
示例:
输入:[3,32,321]
返回值:“321323”
- 解法1:序列全排列
class Solution {
public:
void perm(int pos, vector<int> &num, string &ret) {
if (pos + 1 == num.size()) {
// 一次全排列的结果
string tmp = "";
for (int val : num) {
tmp += to_string(val);
}
ret = min(ret, tmp);
return;
}
for (int i = pos; i < num.size(); ++i) {
swap(num[pos], num[i]);
perm(pos+1, num, ret);
swap(num[pos], num[i]);
}
}
string PrintMinNumber(vector<int> nums) {
string ret(nums.size(), '9'); // nums.size()个'9'
perm(0, nums, ret);
return ret;
}
};
时间复杂度:O(N*N!),全排列的时间复杂度为N!,每次排列结果需要遍历一次nums数组
空间复杂度:O(1)
******************** 使用库函数 ********************
class Solution {
public:
string PrintMinNumber(vector<int> nums) {
vector<string> str;
for (int val : nums) {
str.push_back(to_string(val));
}
sort(str.begin(), str.end());
string ret(nums.size(), '9');
do {
string tmp = "";
for (string val : str)
tmp += val;
ret = min(ret, tmp);
} while (next_permutation(str.begin(), str.end()));
return ret;
}
};
- 解法2:贪心、自定义排序
class Solution {
public:
string PrintMinNumber(vector<int> nums) {
vector<string> str;
for (int val : nums) str.push_back(to_string(val));
sort(str.begin(), str.end(), [](string a, string b) {
return a + b < b + a;
});
string ret = "";
for (string s : str) ret += s;
return ret;
}
};
时间复杂度:O(NlogN), 采用了排序
空间复杂度:O(N)