题目1:求一个数组的最长递减子序列,比如 {9,4,3,2,5,4,3,2} 的最长递减子序列为 {9,5,4,3,2}
动态规划。
假设 0 到 i-1 这段数列的最长递减序列的长度为 s,且这些序列们的末尾值中的最大值是 t。对于 a [i] 有一下情况:
(1) 如果 a [i] 比 t 小,那么将 a [i] 加入任何一个子序列都会使 0 到 i 的最长单调序列长度变成 s+1,这样的话,在 0 到 i 的数列中,长度为 s+1 的递减子序列的末尾值最大值就是 a [i];
(2) 如果 a [i] 和 t 相等,那么说明数列从 0 项到 i 项的最长单调子序列长度就是 s;
(3) 如果 a [i] 比 t 大,那么 a [i] 就不一定能够成为长度为 s 的递减子序列的末项,这取决于长度为 s-1 的各个递减子序列的末尾值的最大值 t'。
如果 t' 比 a [i] 要大,那么就可以形成长度为 s 的递减子序列,如果 t' 比 a [i] 小,那么问题就在往前递推,把 a [i] 和长度为 s-2 的各个递减子序列的末尾值的最大值比较,直到:(1) a [i] 比长度为 s' 的递减子序列的末尾值的最大值要小,那么 a [i] 就是数列 0 到 i 部分长度为 s'+1 的递减子序列的末尾值中的最大值;(2) a [i] 比任何长度的递减子序列的末尾值的最大值都要大,那么 a [i] 就是长度为 1 的递减子序列的最大值。
所以,引入数组 c [i] 表示长度为 i 的递减子序列的末尾值的最大值。显然 c 数组必然是单调递减的。b [i] 数组用于子序列的输出,b [i] 表示从 a [0] 到 a [i] 且终止于 a [i] 的最长递减序列的长度。
算法复杂度:O (nlogn),对于数组 c 的查找使用二分查找,降低了整体的算法复杂度。
算法步骤:
1)读入 n 和 a [i].
2)将数组 c 全部赋值为 - 1.
3)定义变量 s,初始化为 1,s 表示目前为止最长单调序列的长度,同时也是数组 C 的有效容量。c [1] = a [0].
4)对于 0 到 n-1 的每个 i:
查找 c [1] 到 c [s],找到一个值 k 满足下列几种情况:
(1)c [k] <= a [i] 而 c [k-1] > a [i] (如果 k>1)
(2)找不到(1)中 k 的话,k 等于 s+1,并且 s 自加一。
c[k] = a[i];
b[i] = k;
5)最后所得 s 即为所求值。
#include
//函数功能:打印最长子序列
//函数参数:a为源数组,b为存放序列长度的数组,k为最长子序列的末尾元素
//返回值 :无
void print(int *a, int *b, int k){
int i;
for(i=k-1; i>=0; i--){
if(a[i]>a[k] && b[k]==b[i]+1){//当满足b[i] + 1 == b[k] && a[i] > a[k]时,a[i]就是前一个元素
print(a, b, i);//递归,一直到第一个元素
break;
}
}
printf("a[%d]=%d ", k, a[k]);//从第一个满足条件的元素开始打印
}
//函数功能 :一个数组的最长递减子序列
//函数参数 :a指向源数组,len表示数组长度
//返回值 :无
void find_mss(int *a, int len){
int i, j, maxi=0; //maxi用来记录最长递减序列的末尾元素
int *B = new int[len];
for(i=0; i
B[i]=1;
for(j=0;j
if(a[j]>a[i] && B[j]+1>B[i]){
B[i] = B[j]+1;
if(B[i]>B[maxi]){
maxi = i; //更新当前找到的最长递减序列
}
}
}
}
print(a, B, maxi);
}
int main(){
int a[]={9,4,3,2,5,4,3,2};
int aa[]={9,8,6,5,3,5,4,2,1};
find_mss(a, 8);
printf("\n");
find_mss(aa, 9);
printf("\n");
return 0;
}
题目2:输入 N 组父子对,求父子对所组成的二叉树的高度
输入 N 组数,一组数代表一个父子对(如,0 1,0 代表父节点,1 代表子节点),求这 N 组数所组成的二叉树的高度;
例如:
输入:6
0 1
0 2
1 3
1 4
2 5
3 6
输出:4
解题思路:动态规划法,使用一个数组 hight [N] 记录每组数所能组成的二叉树的高度,初始化为全 1 数组,使用一个数组 visited [N] 来记录每组数的访问情况,找出最优子结构:
当 visited [i]=0 时,visited [i]=1,hight [i] = hight [i]+1;
然后,当 matrix [j][0]=matrix [i][1] 且 visited [j]=0 时,hight [j] = hight [i]+1,visited [j]=1;
import java.util.Scanner;public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Scanner scanner = new Scanner(System.in); while(scanner.hasNext()){ int groups = scanner.nextInt(); int[][] matrix = new int[groups][2];for(int i=0;i for(int j=0;j<2;j++){ matrix[i][j] = scanner.nextInt(); } } //动态规划输出处理 System.out.println(maxHightHelper(matrix)); } }//动态规划 public static int maxHightHelper(int[][] matrix){ if(matrix==null||matrix.length==0) return 0; //记录当前组的高度 int[] hight = new int[matrix.length]; for(int i=0;i hight[i] = 1; byte[] visited = new byte[matrix.length]; for(int i=0;i if(visited[i]==0){ visited[i] = 1; hight[i] = hight[i]+1; } for(int j=i+1;j if(matrix[j][0]==matrix[i][1]&&visited[j]==0){ visited[j] = 1; hight[j] = hight[i] +1; } } } //找最大的高度 int max = 0; for(int i=0;i if(max max = hight[i]; } return max; }}
题目3:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串 abc,则输出由字符 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。
这是个递归求解的问题。递归算法有四个特性:(1) 必须有可达到的终止条件,否则程序将陷入死循环;(2)子问题在规模上比原问题小;(3)子问题可通过再次递归调用求解;(4)子问题的解应能组合成整个问题的解。
对于字符串的排列问题。如果能生成 n - 1 个元素的全排列,就能生成 n 个元素的全排列。对于只有 1 个元素的集合,可以直接生成全排列。全排列的递归终止条件很明确,只有 1 个元素时。下面这个图很清楚的给出了递归的过程。
参考代码:解法 1 通过 Permutation_Solution1 (str, 0, n); 解法 2 通过调用 Permutation_Solution2 (str, str) 来求解问题。
1 //函数功能 :求一个字符串某个区间内字符的全排列 2 //函数参数 :pStr为字符串,begin和end表示区间 3 //返回值 :无 4 void Permutation_Solution1(char *pStr, int begin, int end) 5 { 6 if(begin == end - 1) //只剩一个元素 7 { 8 for(int i = 0; i < end; i++) //打印 9 cout< 10 cout< 11 } 12 else 13 { 14 for(int k = begin; k < end; k++) 15 { 16 swap(pStr[k], pStr[begin]); //交换两个字符 17 Permutation_Solution1(pStr, begin + 1, end); 18 swap(pStr[k],pStr[begin]); //恢复 19 } 20 } 21 } 22 23 //函数功能 :求一个字符串某个区间内字符的全排列 24 //函数参数 :pStr为字符串,pBegin为开始位置 25 //返回值 :无 26 void Permutation_Solution2(char *pStr, char *pBegin) 27 { 28 if(*pBegin == '\0') 29 { 30 cout< 31 } 32 else 33 { 34 char *pCh = pBegin; 35 while(*pCh != '\0') 36 { 37 swap(*pBegin, *pCh); 38 Permutation_Solution2(pStr, pBegin + 1); 39 swap(*pBegin, *pCh); 40 pCh++; 41 } 42 } 43 } 44 //提供的公共接口 45 void Permutation(char *pStr) 46 { 47 Permutation_Solution1(pStr, 0, strlen(pStr)); 48 //Permutation_Solution2(pStr,pStr); 49 }
题目4:输入一个字符串,输出该字符串中字符的所有组合。
举个例子,如果输入 abc,它的组合有 a、b、c、ab、ac、bc、abc。
同样是用递归求解。可以考虑求长度为 n 的字符串中 m 个字符的组合,设为 C(n,m)。原问题的解即为 C (n, 1), C (n, 2),...C (n, n) 的总和。对于求 C (n, m),从第一个字符开始扫描,每个字符有两种情况,要么被选中,要么不被选中,如果被选中,递归求解 C (n-1, m-1)。如果未被选中,递归求解 C (n-1, m)。不管哪种方式,n 的值都会减少,递归的终止条件 n=0 或 m=0。
1 //函数功能 :从一个字符串中选m个元素 2 //函数参数 :pStr为字符串, m为选的元素个数, result为选中的 3 //返回值 :无 4 void Combination_m(char *pStr, int m, vector<char> &result) 5 { 6 if(pStr == NULL || (*pStr == '\0'&& m != 0)) 7 return; 8 if(m == 0) //递归终止条件 9 { 10 for(unsigned i = 0; i < result.size(); i++) 11 cout< 12 cout<<endl; 13 return; 14 } 15 //选择这个元素 16 result.push_back(*pStr); 17 Combination_m(pStr + 1, m - 1, result); 18 result.pop_back(); 19 //不选择这个元素 20 Combination_m(pStr + 1, m, result); 21 } 22 //函数功能 :求一个字符串的组合 23 //函数参数 :pStr为字符串 24 //返回值 :无 25 void Combination(char *pStr) 26 { 27 if(pStr == NULL || *pStr == '\0') 28 return; 29 int number = strlen(pStr); 30 for(int i = 1; i <= number; i++) 31 { 32 vector<char> result; 33 Combination_m(pStr, i, result); 34 } 35 }
题目5:一个射击运动员打靶,靶一共有 10 环,连开 10 枪打中 90 环的可能性有多少?
这道题的思路与字符串的组合很像,用递归解决。一次射击有 11 种可能,命中 1 环至 10 环,或脱靶。
1 //函数功能 : 求解number次打中sum环的种数
2 //函数参数 :number为打靶次数,sum为需要命中的环数,result用来保存中间结果,total记录种数
3 //返回值 : 无
4 void ShootProblem_Solution1(int number, int sum, vector<int> &result, int *total)
5 {
6 if(sum < 0 || number * 10 < sum) //加number * 10 < sum非常重要,它可以减少大量的递归,类似剪枝操作
7 return;
8 if(number == 1) //最后一枪
9 {
10 if(sum <= 10) //如果剩余环数小于10,只要最后一枪打sum环就可以了
11 {
12 for(unsigned i = 0; i < result.size(); i++)
13 cout<' ';
14 cout<endl;
15 (*total)++;
16 return;
17 }
18 else
19 return;
20 }
21 for(unsigned i = 0; i <= 10; i++) //命中0-10环
22 {
23 result.push_back(i);
24 ShootProblem_Solution1(number-1, sum-i, result, total); //针对剩余环数递归求解
25 result.pop_back();
26 }
27 }
28 //提供的公共接口
29 void ShootProblem(int number, int sum)
30 {
31 int total = 0;
32 vector<int> result;
33 ShootProblem_Solution1(number, sum, result, &total);
34 cout<<"total nums = "<endl;35 }
题目6:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。数值为 0 或者字符串不是一个合法的数值则返回 0。
这道题要考虑全面,对异常值要做出处理。对于这个题目,需要注意的要点有:
指针是否为空指针以及字符串是否为空字符串;
字符串对于正负号的处理;
输入值是否为合法值,即小于等于 '9',大于等于 '0';
int 为 32 位,需要判断是否溢出;
使用错误标志,区分合法值 0 和非法值 0。
代码中用两个函数来实现该功能,其中标志位 g_nStatus 用来表示是否为异常输出,minus 标志位用来表示是否为负数。
C++:
class Solution {
public:
enum Status{kValid = 0, kInValid};
int g_nStatus = kValid;
int StrToInt(string str) {
g_nStatus = kInValid;
long long num = 0;
const char* cstr = str.c_str();
// 判断是否为指针和是否为空字符串
if(cstr != NULL && *cstr != '\0'){
// 正负号标志位,默认是加号
bool minus = false;
if(*cstr == '+'){
cstr++;
}
else if(*cstr == '-'){
minus = true;
cstr++;
}
if(*cstr != '\0'){
num = StrToIntCore(cstr, minus);
}
}
return (int)num;
}
private:
long long StrToIntCore(const char* cstr, bool minus){
long long num = 0;
while(*cstr != '\0'){
// 判断是否是非法值
if(*cstr >= '0' && *cstr <= '9'){
int flag = minus ? -1 : 1;
num = num * 10 + flag * (*cstr - '0');
// 判断是否溢出,32 位
if((!minus && num > 0x7fffffff) || (minus && num < (signed int)0x80000000)){
num = 0;
break;
}
cstr++;
}
else{
num = 0;
break;
}
}
// 判断是否正常结束
if(*cstr == '\0'){
g_nStatus = kValid;
}
return num;
}
};
Python:
# -*- coding:utf-8 -*-
class Solution:
def StrToInt(self, s):
# write code here
length = len(s)
if length == 0:
return 0
else:
minus = False
flag = False
if s[0] == '+':
flag = True
if s[0] == '-':
flag = True
minus = True
begin = 0
if flag:
begin = 1
num = 0
minus = -1 if minus else 1
for each in s[begin:]:
if each >= '0' and each <= '9':
num = num * 10 + minus * (ord(each) - ord('0'))
else:
num = 0
break
return num
题目7:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。
表示数值的字符串遵循如下模式:
[sign]integral-digits[.[fractional-digits]][e|E[sign]exponential-digits]
其中,('[' 和 ']' 之间的为可有可无的部分)。
在数值之前可能有一个表示正负的 '+' 或者 '-'。接下来是若干个 0 到 9 的数位表示数值的整数部分(在某些小数里可能没有数值的整数部分)。如果数值是一个小数,那么在小数后面可能会有若干个 0 到 9 的数位表示数值的小数部分。如果数值用科学记数法表示,接下来是一个 'e' 或者 'E',以及紧跟着的一个整数(可以有正负号)表示指数。
判断一个字符串是否符合上述模式时,首先看第一个字符是不是正负号。如果是,在字符串上移动一个字符,继续扫描剩余的字符串中 0 到 9 的数位。如果是一个小数,则将遇到小数点。另外,如果是用科学记数法表示的数值,在整数或者小数的后面还有可能遇到 'e' 或者 'E'。
class Solution {
public:
// 数字的格式可以用 A [.[B]][e|EC] 或者.B [e|EC] 表示,
// 其中 A 和 C 都是整数(可以有正负号,也可以没有)
// 而 B 是一个无符号整数
bool isNumeric(char* string)
{
// 非法输入处理
if(string == NULL || *string == '\0'){
return false;
}
// 正负号判断
if(*string == '+' || *string == '-'){
++string;
}
bool numeric = true;
scanDigits(&string);
if(*string != '\0'){
// 小数判断
if(*string == '.'){
++string;
scanDigits(&string);
if(*string == 'e' || *string == 'E'){
numeric = isExponential(&string);
}
}
// 整数判断
else if(*string == 'e' || *string == 'E'){
numeric = isExponential(&string);
}
else{
numeric = false;
}
}
return numeric && *string == '\0';
}
private:
// 扫描数字,对于合法数字,直接跳过
void scanDigits(char** string){
while(**string != '\0' && **string >= '0' && **string <= '9'){
++(*string);
}
}
// 用来潘达 un 科学计数法表示的数值的结尾部分是否合法
bool isExponential(char** string){
++(*string);
if(**string == '+' || **string == '-'){
++(*string);
}
if(**string == '\0'){
return false;
}
scanDigits(string);
// 判断是否结尾,如果没有结尾,说明还有其他非法字符串
return (**string == '\0') ? true : false;
}
};
题目8:求两个正整数的最大公约数。
这是一个很基本的问题,最常见的就是两种方法,辗转相除法和辗转相减法。通式分别为 f (x, y) = f (y, x% y), f (x, y) = f (y, x - y) (x >=y > 0)。根据通式写出算法不难,这里就不给出了。这里给出《编程之美》上的算法,主要是为了减少迭代的次数。
对于 x 和 y,如果 y = k * y1, x= k * x1,那么 f (x, y) = k * f (x1, y1)。另外,如果 x = p * x1,假设 p 为素数,并且 y % p != 0,那么 f (x, y) = f (p * x1, y) = f (x1, y)。取 p = 2。
//函数功能: 求最大公约数
//函数参数: x,y为两个数
//返回值: 最大公约数
int gcd_solution1(int x, int y){
if(y == 0)
return x;
else if(x < y)
return gcd_solution1(y, x);
else
{
if(x&1) //x是奇数
{
if(y&1) //y是奇数
return gcd_solution1(y, x-y);
else //y是偶数
return gcd_solution1(x, y>>1);
}
else //x是偶数
{
if(y&1) //y是奇数
return gcd_solution1(x>>1, y);
else //y是偶数
return gcd_solution1(x>>1, y>>1) << 1;
}
}
}
下面非递归版本:
int gcd_solution2(int x, int y){ int result = 1; while(y) { int t = x; if(x&1) { if(y&1) { x = y; y = t % y; } else y >>= 1; } else { if(y&1) x >>= 1; else { x >>= 1; y >>= 1; result <<= 1; } } } return result * x;}
题目9:把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。
所谓的一个数 m 是另一个数 n 的因子,是指 n 能被 m 整除,也就是 n% m==0。根据丑数的定义,丑数只能被 2、3 和 5 整除。根据丑数的定义,丑数应该是另一个丑数乘以 2、3 或者 5 的结果(1 除外)。因此我们可以创建一个数组,里面的数字是排好序的丑数,每一个丑数都是前面的丑数乘以 2、3 或者 5 得到的。
这个思路的关键问题在于怎样保证数组里面的丑数是排好序的。对乘以 2 而言,肯定存在某一个丑数 T2,排在它之前的每一个丑数乘以 2 得到的结果都会小于已有最大的丑数,在它之后的每一个丑数乘以乘以 2 得到的结果都会太大。我们只需要记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个 T2。对乘以 3 和 5 而言,也存在着同样的 T3 和 T5。
C++:
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if(index < 7){
return index;
}
vector<int> res(index);
for(int i = 0; i < 6; i++){
res[i] = i + 1;
}
int t2 = 3, t3 = 2, t5 = 1;
for(int i = 6; i < index; i++){
res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
while(res[i] >= res[t2] * 2){
t2++;
}
while(res[i] >= res[t3] * 3){
t3++;
}
while(res[i] >= res[t5] * 5){
t5++;
}
}
return res[index - 1];
}
};
注意:1,2,3,4,5,6 都是丑数。所以当 index 小于 7 的时候,直接返回 index 即可。
Python:
# -*- coding:utf-8 -*-
class Solution:
def GetUglyNumber_Solution(self, index):
# write code here
if index < 7:
return index
res = [1, 2, 3, 4, 5, 6]
t2, t3, t5 = 3, 2, 1
for i in range(6, index):
res.append(min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5)))
while res[t2] * 2 <= res[i]:
t2 += 1
while res[t3] * 3 <= res[i]:
t3 += 1
while res[t5] * 5 <= res[i]:
t5 += 1
return res[index - 1]
题目10:任意数分三组,使得每组的和尽量相等.
对于本题,我们可以采用归纳总结的方法。比如,任意数组分成一组,使得每组的和尽量相等。任意的数组分成两组,使得每组的和尽量相等。从这两个基本的问题,我们容易知道,如果数组总和整除分组数的商就是分组后的和(如果可以实现的话,多数情况是接近),当然还有一个极端的情况,比如数组中的最大值,大于这个商,这时候,我们可以使用降纬的方法,把其中一个分组的值设置成这个最大值,对于剩下的值采用同样的思路。(这个使用反证法很容易证明的)
#include #include #include using namespace std;typedef vector<int> vec_int;typedef vector<vector<int> > vec_vec_int;void Output(int nNum){ cout << nNum << " ";}void Output2(const vec_int & vecNum){ for_each(vecNum.begin(), vecNum.end(), Output); cout << endl;}void CalZu(vec_vec_int & vecZu, int nZu, const vec_int & vecNum, int nBegin){ if (vecNum.size() <= nBegin) return; int nSum = 0; int nAvg = 0; for (int i=nBegin; i nSum += vecNum[i]; nAvg = nSum / nZu; if (nSum % nZu != 0) nAvg += 1; if (nAvg < vecNum[nBegin]) { vecZu[nZu-1].push_back(vecNum[nBegin]); CalZu(vecZu, nZu-1, vecNum, nBegin+1); } else { vec_int vecSum(nZu, nAvg); for (int i=nBegin; i int k = 0; int nTmpMin = vecSum[k] - vecNum[i]; for (int j=1; j int nTmp = vecSum[j] - vecNum[i]; if ((nTmpMin < 0 && nTmp >= 0) || (nTmpMin < 0 && nTmpMin < nTmp) || (nTmp > 0 && nTmpMin > nTmp) ) { nTmpMin = nTmp; k = j; } } vecZu[k].push_back(vecNum[i]); vecSum[k] -= vecNum[i]; } } }int main(int argc, char ** argv){ int a[] = {9, 8, 6, 6, 6, 5, 6, 5}; // int a[] = {1003, 1002, 103, 102, 101, 3, 2, 1}; int aLen = sizeof(a) / sizeof(int); vec_int vecNum(a, a+aLen); sort(vecNum.begin(), vecNum.end(), greater<int>()); cout << "Input:" << endl; for_each(vecNum.begin(), vecNum.end(), Output); cout << endl; const int nZu = 3; vec_vec_int vecZu(nZu); CalZu(vecZu, nZu, vecNum, 0); cout << "Output:" << endl; for_each(vecZu.begin(), vecZu.end(), Output2); }
计算机视觉常见面试题型介绍及解答
第一期 | 第二期 | 第三期 | 第四期 | 第五期 |
第六期 | 第七期 | 第八期 | 第九期 | 第十期 | 第11期
腾讯算法工程师笔试真题介绍及解析汇总合集
第一期 | 第二期 | 第三期 | 第四期 | 第五期
阿里算法岗位最新编程题介绍及解析
第一期 | 第二期 | 第三期 | 第四期 | 第五期
第六期 | 第七期
华为研发工程师编程题型介绍及解析
第一期 | 第二期 | 第三期 | 第四期 | 第五期 |
第六期 | 第七期 | 第八期 | 第九期 | 第十期 |
字节跳动校招研发岗位笔试编程题型介绍及解析
第一期 | 第二期 | 第三期 | 第四期 | 第五期
第六期 | 第七期 | 第八期 | 第九期 | 第十期
网易算法工程师笔试编程题型介绍及解析
第一期 | 第二期 | 第三期 | 第四期 | 第五期
第六期 | 第七期 | 第八期 | 第九期 | 第十期
NLP 精华面试专题介绍及解析
第一期 | 第二期 | 第三期 | 第四期 | 第五期 | 第六期
百度数据结构 / 算法面试题型介绍及解析
第一期 | 第二期