1.斐波那契数列
题目描述:
斐波那契数列公式为:f[n] = f[n-1] + f[n-2], 初始值f[0]=0, f[1]=1,目标求f[n]
思路1:
普通递归版求法,这种方法通常和汉诺塔一起被放在课本的递归教学部分,应该是面试官不希望看到的算法。
int Fibonacci(int n) {
if (n==0 || n==1) return n;
return Fibonacci(n-1) + Fibonacci(n-2);
}
时间复杂度:O(2^n)
空间复杂度:递归栈
思路2:
方法二是从上往下递归的然后再从下往上回溯的,最后回溯的时候来合并子树从而求得答案。
那么动态规划不同的是,不用递归的过程,直接从子树求得答案。过程是从下往上。
代码:
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)
优化后的动态规划:
发现计算f[5]的时候只用到了f[4]和f[3], 没有用到f[2]…f[0],所以保存f[2]…f[0]是浪费了空间。
只需要用3个变量即可。
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)
2.旋转数组的最小数字
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路:
采用二分法解答这个问题, mid = low + (high - low)/2 需要考虑三种情况:
- (1)array[mid] > array[high]: 出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1- (2)array[mid] == array[high]: 出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边 还是右边,这时只好一个一个试 。 high = high - 1
- (3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。
high = mid 注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]:
array[low] = 4 ; array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid 但情形(1)中low = mid + 1就不会错误。
class Solution {
public:
int minNumberInRotateArray(vector<int> array) {
int low=0; int high=array.size()-1;
while(low<high){
int mid = low+(high-low)/2;
if(array[mid]>array[high])
low = mid+1;
else if(array[mid]==array[high])
high =high-1;
else
high = mid;
}
return array[low];
}
};
3.数组中出现次数超过一半的数字
题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路1:
把数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(n*logn)并非最优;
代码1:
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty()) return 0;
sort(numbers.begin(),numbers.end());
int middle=numbers[numbers.size()/2];
int count=0;
for(int i=0;i<numbers.size();i++){
if(numbers[i]==middle) ++count;
}
return (count>numbers.size()/2)?middle:0;
}
};
思路2:
如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
代码2:
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.empty()) return 0;
int result=numbers[0];
int count=1;
for(int i=1;i<numbers.size();i++){
if(count==0){
result=numbers[i]; //更新result值为当前元素,并置次数为1
count=1;
}
else if(numbers[i]==result)
count++;
else count--;
}
//判断result是否符合条件,即个数是否大于数组长度一半
count=0;
for(int i=0;i<numbers.size();i++){
if(numbers[i]==result) count++;
}
return (count>numbers.size()/2)? result:0;
}
};
4.调整数组顺序 使奇数在前,偶数在后
题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路1:
开辟新数组,遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。最后再进行一次copy。
代码:
class Solution {
public:
void reOrderArray(vector<int> &array) {
vector<int> res;
for(int i=0;i<array.size();i++){
if(array[i]%2==1)
res.push_back(array[i]);
}
for(int i=0;i<array.size();i++){
if(array[i]%2==0)
res.push_back(array[i]);
}
array=res;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
思路2:
初始化操作:记录一个变量i表示已经将奇数放好的下一个位置,显然最开始i=0,表示还没有一个奇数放好。
j 表示数组的下标,初始值为0, 表示从下标0开始遍历。
- 如果遇到偶数,j++
- 如果遇到奇数,假设位置为j,就将此奇数插入到i所指的位置,然后i往后移动一个位置,在插入之前。
- 显然会涉及到数据的移动,也就是将[i,j-1]整体往后移动。直到整个数组遍历完毕,结束。
代码:
class Solution {
public:
void reOrderArray(vector<int> &array) {
int i = 0;
for (int j=0; j<array.size(); ++j) {
if (array[j]&1) {
int tmp = array[j];
for (int k=j-1; k>=i; --k) {
array[k+1] = array[k];//前一个值替换后一个值,从第j-1个向前推到第i个
}
array[i++] = tmp;
}
}
}
};
时间复杂度:O(n^2),
假设数组中一般偶数在前,一半奇数在后,每次都要移动n/2个元素,是n^2/4
空间复杂度:O(1)