本文整理自:http://blog.csdn.net/v_july_v/article/details/6543438
1. 把二元查找树转变成排序的双向链表
题目:
输入一棵二元查找树,将该转换成个排 序的双向链表。
要求不能创建任何新的结点,只调整指针向。
10
/ \
6 14
/ \ / \
4 8 12 16
转换成双向链表
4=6=8=10=12=14=16
首先我们定义的二元查找树节点的数据结构如下:
struct BSTreeNode
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
解答:
这是一个可以使用递归的传统问题。显然可以使用中序遍历(左-根-右)。按照这个方式遍历树,比较小的结点先访问。如果我们每访问一个结点,假设之前访问过的结点已经调整成一个排序双向链表,我们再把调整当前结点的指针将其链接到链表的末尾。当所有结点都访问过之后,整棵树也就转换成一个排序双向链表了。
BSTreeNode *pHead=NULL;
BSTreeNode *pListIndex=NULL;
// 遍历二元查找树 中序
void ergodicBSTree(BSTreeNode * pCurrent)
{
if (NULL == pCurrent) {
return;
}
if (NULL != pCurrent->m_pLeft) {
ergodicBSTree(pCurrent->m_pLeft);
}
// 节点接到链表尾部
convertToDoubleList(pCurrent);
// 右子树为空
if (NULL != pCurrent->m_pRight) {
ergodicBSTree(pCurrent->m_pRight);
}
}
// 二叉树转换成list
void convertToDoubleList(BSTreeNode * pCurrent)
{
pCurrent->m_pLeft = pListIndex;
if (NULL != pListIndex) {
pListIndex->m_pRight = pCurrent;
} else {
pHead = pCurrent;
}
pListIndex = pCurrent;
cout<m_nValue<
2.设计包含min 函数的栈。定义栈的数据结构,要求添加一个min 函数,能够得到栈的最小元素。要求函数min、push 以及pop 的时间复杂度都是O(1)。
ANSWER:
Stack is a LIFO data structure. When some element is popped from the stack, the status will recover to the original status as before that element was pushed. So we can recover the minimum element, too.
#define STACK_LEN 50
typedef struct
{
int val;
int min;
} stack_item;
typedef struct
{
stack_item data[STACK_LEN];
int top;
} stack;
void push(stack *stk, int val)
{
stk->data[++stk->top].val = val;
if (stk->top > 0)
{
if (val < stk->data[stk->top - 1].min)
//如果当前push进的元素小于栈中最小元素
stk->data[stk->top].min = val; //把当前元素置为栈中最小元素
else
//否则,不更新
stk->data[stk->top].min = stk->data[stk->top - 1].min;
}
else
stk->data[stk->top].min = val;
}
int pop(stack *stk)
{
return stk->data[stk->top--].val;
}
int min(stack *stk)
{
return stk->data[stk->top].min;
}
3.求子数组的最大和
int maxSubarray(int a[], int size) {
if (size<=0) error(“error array size”);
int sum = 0;
int max = - (1 << 31);
int cur = 0;
while (cur < size) {
sum += a[cur++];
if (sum > max) {
max = sum;
} else if (sum < 0) {
sum = 0;
}
}
return max;
}
4.在二元树中找出和为某一值的所有路径
struct BinaryTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BinaryTreeNode *m_pLeft; // left child of node
BinaryTreeNode *m_pRight; // right child of node
};
ANSWER:struct TreeNode {
int data;
TreeNode * left;
TreeNode * right;
};
void printPaths(TreeNode * root, int sum) {
int path[MAX_HEIGHT];
helper(root, sum, path, 0);
}
void helper(TreeNode * root, int sum, int path[], int top) {
path[top++] = root.data;
sum -= root.data;
if (root->left == NULL && root->right==NULL) {
if (sum == 0) printPath(path, top);
} else {
if (root->left != NULL) helper(root->left, sum, path, top);
if (root->right!=NULL) helper(root->right, sum, path, top);
}
top --;
sum -= root.data;
}
5.查找最小的k 个元素
参考我的文章“堆结构的运用”:http://blog.csdn.net/zhoudaxia/article/details/5669914
插入排序参考我的文章“排序算法的实现”:http://blog.csdn.net/zhoudaxia/article/details/4586285
第6 题
腾讯面试题:
给你10 分钟时间,根据上排给出十个数,在其下排填出对应的十个数
要求下排每个数都是先前上排那十个数在下排出现的次数。
上排的十个数如下:
【0,1,2,3,4,5,6,7,8,9】
举一个例子,
数值: 0,1,2,3,4,5,6,7,8,9
分配: 6,2,1,0,0,0,1,0,0,0
0 在下排出现了6 次,1 在下排出现了2 次,
2 在下排出现了1 次,3 在下排出现了0 次....
以此类推..
说实话,这题除了一次次迭代尝试外,我没想到什么好的处理办法。假设上排的数组为a,下排对应的数组为b,元素个数为n,按照题目的意思可以得到以下公式:
b1+b2+...+bn=na1*b1+a2*b2+...+an*bn=nb
中的元素来源于a这种一次多项表达式的求解我不会。
我觉得这题比实际上还要复杂,以下情况是必须要考虑的:
1、上排的数据元素并不一定是排序的。
2、上排的数据元素并不一定就有0,可能还有负数。
3、上排的数据元素可能会有重复的。
4、未必有对应的下排数据。
#include
#define len 10
class NumberTB
{
private:
int top[len];
int bottom[len];
bool success;
public:
NumberTB();
int* getBottom();
void setNextBottom();
int getFrequecy(int num);
};
NumberTB::NumberTB()
{
success = false;
//format top
for(int i=0;i
除了上面提到的,就楼主的程序而言,个人觉得有以下几个改进建议:
1、类中success是个多余的东西,如果设置这么一个成员变量,就不应该在函数setNextBottom中再无谓多一个reB变量。
2、由于未必有解,getBottom可能限于死循环。
3、getBottom中变量i完全是多余的。
4、getFrequecy中判断有些是多余的,可以考虑把我上面提到的公司考虑进去。
等有时间了,我再好好考虑如何写个比较好的程序。
第7 题
微软亚院之编程判断俩个链表是否相交。给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表均不带环。
问题扩展:
1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?
ANSWER:
该问题的分析可参考本博客的文章“判断单链表是否存在环以及两个链表是否相交 ”:http://blog.csdn.net/zhoudaxia/article/details/8884402
struct Node {
int data;
int Node *next;
};
// if there is no cycle.
int isJoinedSimple(Node * h1, Node * h2) {
while (h1->next != NULL) {
h1 = h1->next;
}
while (h2->next != NULL) {
h2 = h2-> next;
}
return h1 == h2;
}
// if there could exist cycle
int isJoined(Node *h1, Node * h2) {
Node* cylic1 = testCylic(h1);
Node* cylic2 = testCylic(h2);
if (cylic1+cylic2==0) return isJoinedSimple(h1, h2);
if (cylic1==0 && cylic2!=0 || cylic1!=0 &&cylic2==0) return 0;
Node *p = cylic1;
while (1) {
if (p==cylic2 || p->next == cylic2) return 1;
p=p->next->next;
cylic1 = cylic1->next;
if (p==cylic1) return 0;
}
}
Node* testCylic(Node * h1) {
Node * p1 = h1, *p2 = h1;
while (p2!=NULL && p2->next!=NULL) {
p1 = p1->next;
p2 = p2->next->next;
if (p1 == p2) {
return p1;
}
}
return NULL;
}
第8 题
Node * reverse(Node * head) {
if (head == NULL) return head;
if (head->next == NULL) return head;
Node * ph = reverse(head->next);
head->next->next = head;
head->next = NULL;
return ph;
}
Node * reverseNonrecurisve(Node * head) {
if (head == NULL) return head;
Node * p = head;
Node * previous = NULL;
while (p->next != NULL) {
p->next = previous;
previous = p;
p = p->next;
}
p->next = previous;
return p;
}
★用一种算法在一个循环的链接表里插入一个节点,但不得穿越链接表。
int match(char * str, char * ptn) {
if (*ptn == ‘\0’) return 1;
if (*ptn == ‘*’) {
do {
if (match(str++, ptn+1)) return 1;
} while (*str != ‘\0’);
return 0;
}
if (*str == ‘\0’) return 0;
if (*str == *ptn || *ptn == ‘?’) {
return match(str+1, ptn+1);
}
return 0;
}
★颠倒一个字符串。优化速度。优化空间。
void reverse(char *str) {
reverseFixlen(str, strlen(str));
}
void reverseFixlen(char *str, int n) {
char* p = str+n-1;
while (str < p) {
char c = *str;
*str = *p; *p=c;
}
}
★颠倒一个句子中的词的顺序,比如将“我叫克丽丝”转换为“克丽丝叫我”,
实现速度最快,移动最少。
ANSWER:
Reverse the whole string, then reverse each word. Using the reverseFixlen() above.
void reverseWordsInSentence(char * sen) {
int len = strlen(sen);
reverseFixlen(sen, len);
char * p = str;
while (*p!=’\0’) {
while (*p == ‘ ‘ && *p!=’\0’) p++;
str = p;
while (p!= ‘ ‘ && *p!=’\0’) p++;
reverseFixlen(str, p-str);
}
}
★找到一个子字符串。优化速度。优化空间。
ANSWER:
KMP? BM? Sunday? Using BM or sunday, if it’s ASCII string, then it’s easy to fast access the auxiliary array. Otherwise an hashmap or bst may be needed. Lets assume it’s an ASCII string.
int bm_strstr(char *str, char *sub) {
int len = strlen(sub);
int i;
int aux[256];
memset(aux, sizeof(int), 256, len+1);
for (i=0; i=0 && str[j--] == sub[k--])
;
if (k<0) return j+1;
if (i+1
However, this algorithm, as well as BM, KMP algorithms use O(|sub|) space. If this is not acceptable, Rabin-carp algorithm can do it. Using hashing to fast filter out most false matchings.
#define HBASE 127
int rc_strstr(char * str, char * sub) {
int dest= 0;
char * p = sub;
int len = 0;
int TO_REDUCE = 1;
while (*p!=’\0’) {
dest = HBASE * dest + (int)(*p);
TO_REDUCE *= HBASE;
len ++;
}
int hash = 0;
p = str;
int i=0;
while (*p != ‘\0’) {
if (i++=len && strncmp(sub, p-len+1, len) == 0) return i-len;
p++;
}
return -1;
}
★比较两个字符串,用O(n)时间和恒量空间。
int strcmp(char * p1, char * p2) {
while (*p1 != ‘\0’ && *p2 != ‘\0’ && *p1 == *p2) {
p1++, p2++;
}
if (*p1 == ‘\0’ && *p2 == ‘\0’) return 0;
if (*p1 == ‘\0’) return -1;
if (*p2 == ‘\0’) return 1;
return (*p1 - *p2); // it can be negotiated whether the above 3 if’s are necessary, I don’t like to omit them.
}
★ 假设你有一个用1001 个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1 到1000(包括1000)之间。此外,除一个数字出现两次外,其他所有数字只出现一次。假设你只能对这个数组做一次处理,用一种算法找出重复的那个数 字。如果你在运算中使用了辅助的存储方式,那么你能找到不用这种方式的算法吗?
int findX(int a[]) {
int k = a[0];
for (int i=1; i<=1000;i++)
k ~= a[i]~i;
}
return k;
}
★不用乘法或加法增加8 倍。现在用同样的方法增加7 倍。
第9 题
判断整数序列是不是二元查找树的后序遍历结果
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。
如果是返回true,否则返回false。
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
8
/ \
6 10
/ \ / \
5 7 9 11
因此返回true。
如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
ANSWER:
传统问题。根据遍历结果(后序/中序/前序)来构造二叉查找树,涉及二叉树递归是首选。对后序遍历,最后一个元素就是树的根,然后通过根把数组分割成左子树和右子树两部分,分割点在数组第一个大于根的元素处(因为左子树所有元素小于根,右子树所有元素大于根),然后对左右子树进行递归遍历。其实,就是一个后序遍历二叉树的算法。
bool verifySquenceOfBST(int squence[], int length)
{
if(squence == NULL || length <= 0)
return false;
// root of a BST is at the end of post order traversal squence
int root = squence[length - 1];
// the nodes in left sub-tree are less than the root
int i = 0;
for(; i < length - 1; ++ i)
{
if(squence[i] > root)
break;
}
// the nodes in the right sub-tree are greater than the root
int j = i;
for(; j < length - 1; ++ j)
{
if(squence[j] < root)
return false;
}
// verify whether the left sub-tree is a BST
bool left = true;
if(i > 0)
left = verifySquenceOfBST(squence, i);
// verify whether the right sub-tree is a BST
bool right = true;
if(i < length - 1)
right = verifySquenceOfBST(squence + i, length - i - 1);
return (left && right);
}
第10 题
ANSWER:
可以利用堆栈后进先出的特性,把输入先push到堆栈中,然后再pop出来,即反序。
#include
#include
using namespace std;
class ReverseWords{
public:
ReverseWords(string* wo):words(wo){}
void reverse_()
{
int length=words->size();
int begin=-1,end=-1;
for(int i=0;iat(i)==' ')
continue;
if(begin==-1)
{
begin=i;
continue;
}
if(words->at(i)==' ')
end=i-1;
else if(i==length-1)
end=i;
else
continue;
reverse__(begin,end); //1.字母翻转
begin=-1,end=-1;
}
reverse__(0,length-1); //2.单词翻转
}
private:
void reverse__(int begin,int end) //
{
while(beginat(begin);
words->at(begin)=words->at(end);
words->at(end)=t;
++begin;
--end;
}
}
string* words;
};
int main(){
string s="I am a student.";
ReverseWords r(&s);
r.reverse_();
cout<
运行结果:
student. a am I
第11题
求二叉树中节点的最大距离...
如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,我们姑且定义"距离"为两节点之间边的个数。写一个程序,求一棵二叉树中相距最远的两个节点之间的距离。
ANSWER:
This is interesting... Also recursively, the longest distance between two nodes must be either from root to one leaf, or between two leafs. For the former case, it’s the tree height. For the latter case, it should be the sum of the heights of left and right subtrees of the two leaves’ most least ancestor.
The first case is also the sum the heights of subtrees, just the height + 0.
int maxDistance(Node * root) {
int depth;
return helper(root, depth);
}
int helper(Node * root, int &depth) {
if (root == NULL) {
depth = 0; return 0;
}
int ld, rd;
int maxleft = helper(root->left, ld);
int maxright = helper(root->right, rd);
depth = max(ld, rd)+1;
return max(maxleft, max(maxright, ld+rd));
}
第12 题
方案1:
1+..+n=n*(n+1)/2=(n^2+n)/2
it is easy to get x/2, so the problem is to get n^2
though no if/else is allowed, we can easilly go around using short-pass. using macro to make it fancier:
#define T(X, Y, i) (Y & (1<> 1;
}
方案2:
循环只是让相同的代码执行n遍而已,我们完全可以不用for和while达到这个效果。比如定义一个类,我们new一个含有n个这种类型元素的数组,那么该类的构造函数将确定会被调用n次。我们可以将需要执行的代码放到构造函数里。
#include
class Temp
{
public:
Temp()
{
++N;
Sum += N;
}
static void Reset() { N = 0; Sum = 0; }
static int GetSum() { return Sum; }
private:
static int N;
static int Sum;
};
int Temp::N = 0;
int Temp::Sum = 0;
int solution1_Sum(int n)
{
Temp::Reset();
Temp *a = new Temp[n]; //就是这个意思,new出n个数组。
delete []a;
a = 0;
return Temp::GetSum();
}
int main()
{
cout<
运行结果:
方案3:
用递归,结合C++的虚函数调用来实现递归终止。既然不能判断是不是应该终止递归,我们不妨定义两个函数。一个函数充当递归函数的角色,另一个函数处理终止递归的情况,我们需要做的就是在两个函数里二选一。从二选一我们很自然的想到布尔变量,比如ture/(1)的时候调用第一个函数,false/(0)的时候调用第二个函数。那现在的问题是如和把数值变量n转换成布尔值。如果对n连续做两次反运算,即!!n,那么非零的n转换为true,0转换为false。
#include
class A;
A* Array[2];
class A
{
public:
virtual int Sum (int n) { return 0; }
};
class B: public A
{
public:
virtual int Sum (int n) { return Array[!!n]->Sum(n-1)+n; }
};
int solution2_Sum(int n)
{
A a;
B b;
Array[0] = &a;
Array[1] = &b;
int value = Array[1]->Sum(n);
//利用虚函数的特性,当Array[1]为0时,即Array[0] = &a; 执行A::Sum,
//当Array[1]不为0时, 即Array[1] = &b; 执行B::Sum。
return value;
}
int main()
{
cout<
方案4:
刚看到这个题目,我的第一想法就是多半用递规解决,接下来难点就是递规如何结束。题目的要求是比较苛刻的,楼主答案里面用的两种方法确实挺巧妙的,但我不会C++,因此只能另外想办法。下面是我写的代码:
int sum(int n)
{
int val = 0;
n > 0 && (val = n + sum(n - 1));
return val;
}
虽然功能是实现了,但这个代码编译时是有警告的,当然这个警告不重要。但我总觉得有更加合适的方法。
方案5:
模板元编程,最快捷的计算方式,编译期完成计算。
#include
using namespace std;
template
struct CalCls
{
enum {sum = CalCls::sum + N};
};
//模板特化,用于终止递归
template<>
struct CalCls<0>
{
enum {sum = 0};
};
int main()
{
cout<<"1+2+3+...+100 = "<::sum<
第13 题:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
答案:
思路1:遍历一次记录链表长度n,然后重新再遍历n-k步。
思路2:稍微有点经验的同志都会想到一种典型的套路。构造两个步长差为k的指针,然后同时移动。设置两个指针p1,p2,首先p1和p2都指向head,p2向前走k步,这样p1和p2之间就间隔k个节点,然后p1和p2同时移动,p2到尾部时,p1到达目标节点。
两种思路的时间复杂度完全相等。
Node * lastK(Node * head, int k) {
if (k<0) error(“k < 0”);
Node *p=head, *pk=head;
for (;k>0;k--) {
if (pk->next!=NULL) pk = pk->next;
else return NULL;
}
while (pk->next!=NULL) {
p=p->next, pk=pk->next;
}
return p;
}
第14 题
void find2Number(int a[], int n, int dest) {
int *f = a, *e=a+n-1;
int sum = *f + *e;
while (sum != dest && f < e) {
if (sum < dest) sum = *(++f);
else sum = *(--e);
}
if (sum == dest) printf(“%d, %d\n”, *f, *e);
}
第15 题:对树做镜像翻转
struct BSTreeNode // a node in the binary search tree (BST)
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
ANSWER:void swap(Node ** l, Node ** r) {
Node * p = *l;
*l = *r;
*r = p;
}
void mirror(Node * root) {
if (root == NULL) return;
swap(&(root->left), &(root->right));
mirror(root->left);
mirror(root->right);
}
void mirrorIteratively(Node * root) {
if (root == NULL) return;
stack buf;
buf.push(root);
while (!stack.empty()) {
Node * n = stack.pop();
swap(&(root->left), &(root->right));
if (root->left != NULL) buf.push(root->left);
if (root->right != NULL) buf.push(root->right);
}
}
第16 题:
利用队列做层次遍历,树的层次遍历实际上是广度优先遍历。
由于STL已经为我们实现了一个很好的deque(两端都可以进出的队列),我们只需要拿过来用就可以了。
我们知道树是图的一种特殊退化形式。同时如果对图的深度优先遍历和广度优先遍历有比较深刻的理解,将不难看出这种遍历方式实际上是一种广度优先遍历。因此这道题的本质是在二元树上实现广度优先遍历。
#include
#include
using namespace std;
struct BTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BTreeNode *m_pLeft; // left child of node
BTreeNode *m_pRight; // right child of node
};
BTreeNode* pListIndex;
BTreeNode* pHead;
void PrintFromTopToBottom(BTreeNode *pTreeRoot)
{
if(!pTreeRoot)
return;
// get a empty queue
deque dequeTreeNode;
// insert the root at the tail of queue
dequeTreeNode.push_back(pTreeRoot);
while(dequeTreeNode.size())
{
// get a node from the head of queue
BTreeNode *pNode = dequeTreeNode.front();
dequeTreeNode.pop_front();
// print the node
cout << pNode->m_nValue << ' ';
// print its left child sub-tree if it has
if(pNode->m_pLeft)
dequeTreeNode.push_back(pNode->m_pLeft);
// print its right child sub-tree if it has
if(pNode->m_pRight)
dequeTreeNode.push_back(pNode->m_pRight);
}
}
// 创建二元查找树
void addBTreeNode(BTreeNode * & pCurrent, int value)
{
if (NULL == pCurrent)
{
BTreeNode * pBTree = new BTreeNode();
pBTree->m_pLeft = NULL;
pBTree->m_pRight = NULL;
pBTree->m_nValue = value;
pCurrent = pBTree;
}
else
{
if ((pCurrent->m_nValue) > value)
{
addBTreeNode(pCurrent->m_pLeft, value);
}
else if ((pCurrent->m_nValue) < value)
{
addBTreeNode(pCurrent->m_pRight, value);
}
}
}
int main()
{
BTreeNode * pRoot = NULL;
pListIndex = NULL;
pHead = NULL;
addBTreeNode(pRoot, 8);
addBTreeNode(pRoot, 6);
addBTreeNode(pRoot, 5);
addBTreeNode(pRoot, 7);
addBTreeNode(pRoot, 10);
addBTreeNode(pRoot, 9);
addBTreeNode(pRoot, 11);
PrintFromTopToBottom(pRoot);
return 0;
}
第17 题:
思路剖析:
由于题目与字符出现的次数相关,我们可以统计每个字符在该字符串中出现的次数。要达到这个目的,需要一个数据容器来存放每个字符的出现次数。在这个数据容器中可以根据字符来查找它出现的次数,也就是说这个容器的作用是把一个字符映射成一个数字。在常用的数据容器中,哈希表正是这个用途。由于本题的特殊性,我们只需要一个非常简单的哈希表就能满足要求。
由于字符(char)是一个长度为8的数据类型,因此总共有可能256 种可能。于是我们创建一个长度为256的数组,每个字母根据其ASCII码值作为数组的下标对应数组的对应项,而数组中存储的是每个字符对应的次数。这样我们就创建了一个大小为256,以字符ASCII码为键值的哈希表。我们第一遍扫描这个数组时,每碰到一个字符,在哈希表中找到对应的项并把出现的次数增加一次。这样在进行第二次扫描时,就能直接从哈希表中得到每个字符出现的次数了。
#include
#include
char FirstNotRepeatingChar(char* pString)
{
if(!pString)
return 0;
const int tableSize = 256;
unsigned int hashTable[tableSize];
//初始化hash表
for(unsigned int i = 0; i < tableSize; ++ i)
hashTable[i] = 0;
char* pHashKey = pString;
while(*(pHashKey) != '\0')
hashTable[*(pHashKey++)] ++;
pHashKey = pString;
while(*pHashKey != '\0')
{
if(hashTable[*pHashKey] == 1)
return *pHashKey;
pHashKey++;
}
return *pHashKey;
}
int main()
{
cout<<"请输入一串字符:"<>s;
char* ps=s;
cout<
第18 题:
简单的约瑟夫循环问题。先看这个题目的简单变形。
n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那个人?
ANSWER:
一般的解答:
#include
int main()
{
int i,k,m,n,num[50],*p;
printf("input number of person:n=");
scanf("%d",&n);
printf("input number of the quit:m=");
scanf("%d",&m);
p=num;
for(i=0;i
另一种更优雅的方案:The keys are:
1) if we shift the ids by k, namely, start from k instead of 0, we should add the result by k%n
2) after the first round, we start from k+1 ( possibly % n) with n-1 elements, that is equal to an (n-1) problem while start from (k+1)th element instead of 0, so the answer is (f(n-1, m)+k+1)%n
3) k = m-1, so f(n,m)=(f(n-1,m)+m)%n.
finally, f(1, m) = 0;
Now this is a O(n) solution.
int joseph(int n, int m) {
int fn=0;
for (int i=2; i<=n; i++) {
fn = (fn+m)%i; }
return fn;
}
第19 题:
在很多C 语言教科书中讲到递归函数的时候,都会用Fibonacci 作为例子。因此很多程序员对这道题的递归解法非常熟悉,但这并不能说明递归解法最适合这道题目。更简单的办法是从下往上计算,首先根据f(0)和f(1)算出f(2),再根据f(1)和f(2)算出f(3)……,依此类推就可以算出第n项了。很容易理解,这种思路的时间复杂度是O(n)。其实,就是转化为非递归程序,用递推。
long long Fibonacci_Solution2(unsigned n)
{
int result[2] = {0, 1};
if(n < 2)
return result[n];
long long fibNMinusOne = 1;
long long fibNMinusTwo = 0;
long long fibN = 0;
for(unsigned int i = 2; i <= n; ++ i)
{
fibN = fibNMinusOne + fibNMinusTwo;
fibNMinusTwo = fibNMinusOne;
fibNMinusOne = fibN;
}
return fibN;
}
很可惜,这还不是最快的方法。还有一种方法,可达到时间复杂度为O(log n)。可以用矩阵连乘得到: a[2][2]={1,1,1,0} a连乘n次,得到的a[0][1]和a[1][0]就是第n项。因为是连乘,所有可以用快速连乘(秦九韶),复杂度就可以是logN了。
let A=
{1 1}代码如下:
int f(int n) {
int A[4] = {1,1,1,0};
int result[4];
power(A, n, result);
return result[0];
}
void multiply(int[] A, int[] B, int _r) {
_r[0] = A[0]*B[0] + A[1]*B[2];
_r[1] = A[0]*B[1] + A[1]*B[3];
_r[2] = A[2]*B[0] + A[3]*B[2];
_r[3] = A[2]*B[1] + A[3]*B[3];
}
void power(int[] A, int n, int _r) {
if (n==1) { memcpy(A, _r, 4*sizeof(int)); return; }
int tmp[4];
power(A, n>>1, _r);
multiply(_r, _r, tmp);
if (n & 1 == 1) {
multiply(tmp, A, _r);
} else {
memcpy(_r, tmp, 4*sizeof(int));
}
}
第20题:
此题一点也不简单。不信,你就先不看一下的代码,你自己先写一份,然后再对比一下,便知道了。
1. 转换的思路:每扫描到一个字符,我们把在之前得到的数字乘以10再加上当前字符表示的数字。这个思路用循环不难实现。
2. 由于整数可能不仅仅之含有数字,还有可能以'+'或者'-'开头,表示整数的正负。如果第一个字符是'+'号,则不需要做任何操作;如果第一个字符是'-'号,则表明这个整数是个负数,在最后的时候我们要把得到的数值变成负数。
3. 接着我们试着处理非法输入。由于输入的是指针,在使用指针之前,我们要做的第一件是判断这个指针是不是为空。如果试着去访问空指针,将不可避免地导致程序崩溃。
4. 输入的字符串中可能含有不是数字的字符。每当碰到这些非法的字符,我们就没有必要再继续转换。
5. 最后一个需要考虑的问题是溢出问题。由于输入的数字是以字符串的形式输入,因此有可能输入一个很大的数字转换之后会超过能够表示的最大的整数而溢出。
enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;
int StrToInt(const char* str)
{
g_nStatus = kInvalid;
long long num = 0;
if(str != NULL)
{
const char* digit = str;
// the first char in the string maybe '+' or '-'
bool minus = false;
if(*digit == '+')
digit ++;
else if(*digit == '-')
{
digit ++;
minus = true;
}
// the remaining chars in the string
while(*digit != '\0')
{
if(*digit >= '0' && *digit <= '9')
{
num = num * 10 + (*digit - '0');
// overflow
if(num > std::numeric_limits::max())
{
num = 0;
break;
}
digit ++;
}
// if the char is not a digit, invalid input
else
{
num = 0;
break;
}
}
if(*digit == '\0')
{
g_nStatus = kValid;
if(minus)
num = 0 - num;
}
}
return static_cast(num);
}
第21 题
输入两个整数n 和m,从数列1,2,3.......n 中随意取几个数,使其和等于m,要求将其中所有的可能组合列出来。
答案:
这是一个组合产生问题。与第14题差不多。可以使用递归。
// 递归方法
//这个,没有任何问题
//from yansha
//July、updated。
#include
#include
using namespace std;
listlist1;
void find_factor(int sum, int n)
{
// 递归出口
if(n <= 0 || sum <= 0)
return;
// 输出找到的结果
if(sum == n)
{
// 反转list
list1.reverse();
for(list::iterator iter = list1.begin(); iter != list1.end(); iter++)
cout << *iter << " + ";
cout << n << endl;
list1.reverse();
}
list1.push_front(n); //典型的01背包问题
find_factor(sum-n, n-1); //放n,n-1个数填满sum-n
list1.pop_front();
find_factor(sum, n-1); //不放n,n-1个数填满sum
}
int main()
{
int sum, n;
cout << "请输入你要等于多少的数值sum:" << endl;
cin >> sum;
cout << "请输入你要从1.....n数列中取值的n:" << endl;
cin >> n;
cout << "所有可能的序列,如下:" << endl;
find_factor(sum,n);
return 0;
}
第22 题:
4张r,4张b,有以下3种组合:rr bb rb
1. B,C全是一种颜色
B C A
bb.rr bb.rr
2. B C A
bb rr bb/RR/BR,=>A:BR
rr bb =>A:BR
3. B C A
BR BB RR/BR, =>A:BR
推出A:BR的原因:如果 A是RR,那么当ABC都说不知道后,B最后应该知道自己是BR了。因为B不可能是RR或BB。
4. B C A
BR BR BB/RR/BR
推出A:BR的原因:
//i、 如果A是BB,那么B=>BR/RR,如果B=>RR,那么一开始,C就该知道自己是BR了(A俩蓝,B俩红)。(如果C,A俩蓝,那么B就一开始知道,如果C.B俩红,那么A一开始就知道,所以论证前头,当B=>RR,那么一开始,C就该知道自己是BR)。如果B=>BR,那么同样道理,C一开始也该知道自己是BR了。
//ii、 如果A是RR,类似分析。
//iii、最后,也还是推出=>A:BR
至于程序,暂等高人。
第23 题:
用最简单,最快速的方法计算出下面这个圆形是否和正方形相交。"
3D 坐标系原点(0.0,0.0,0.0)
圆形:
半径r = 3.0
圆心o = (*.*, 0.0, *.*)
正方形:
4 个角坐标;
1:(*.*, 0.0, *.*)
2:(*.*, 0.0, *.*)
3:(*.*, 0.0, *.*)
4:(*.*, 0.0, *.*)
ANSWER
Crap... I totally cannot understand this problem... Does the *.* represent any possible number?
第24 题:
链表操作
(1).单链表就地逆置,
(2)合并链表
ANSWER
Reversing a linked list. Already done.
What do you mean by merge? Are the original lists sorted and need to be kept sorted? If not, are there any special requirements? I will only do the sorted merging.
就地逆转:
ListNode* ReverseIteratively(ListNode* pHead)
{
ListNode* pReversedHead = NULL;
ListNode* pNode = pHead;
ListNode* pPrev = NULL;
while(pNode != NULL) //pNode<=>m
{
ListNode* pNext = pNode->m_pNext; //n保存在pNext下
//如果pNext指为空,则当前结点pNode设为头。
if(pNext == NULL)
pReversedHead = pNode;
//reverse the linkage between nodes
pNode->m_pNext = pPrev;
//move forward on the the list
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
合并有序链表:
Node * merge(Node * h1, Node * h2) {
if (h1 == NULL) return h2;
if (h2 == NULL) return h1;
Node * head;
if (h1->data>h2->data) { //头结点指向小的节点
head = h2; h2=h2->next;
} else {
head = h1; h1=h1->next;
}
Node * current = head;
while (h1 != NULL && h2 != NULL) { //把小的节点先链入当前链表
if (h1 == NULL || (h2!=NULL && h1->data>h2->data)) {
current->next = h2; h2=h2->next; current = current->next;
} else {
current->next = h1; h1=h1->next; current = current->next;
}
}
current->next = NULL;
return head;
}
第25 题:
int continumax(char *outputstr, char *inputstr) {
int len = 0;
char * pstart = NULL;
int max = 0;
while (1) {
if (*inputstr >= ‘0’ && *inputstr <=’9’) {
len ++;
} else {
if (len > max) pstart = inputstr-len;
len = 0;
}
if (*inputstr++==’\0’) break;
}
for (int i=0; i
26.左旋转字符串
如果不考虑时间和空间复杂度的限制,最简单的方法莫过于把这道题看成是把字符串分成前后两部分,通过旋转操作把这两个部分交换位置。于是我们可以新开辟一块长度为n+1的辅助空间,把原字符串后半部分拷贝到新空间的前半部分,在把原字符串的前半部分拷贝到新空间的后半部分。不难看出,这种思路的时间复杂度是O(n),需要的辅助空间也是O(n)。
把字符串看成有两段组成的,记位XY。左旋转相当于要把字符串XY变成YX。我们先在字符串上定义一种翻转的操作,就是翻转字符串中字符的先后顺序。把X翻转后记为XT。显然有(XT)T=X。我们首先对X和Y两段分别进行翻转操作,这样就能得到XTYT。接着再对XTYT进行翻转操作,得到(XTYT)T=(YT)T(XT)T=YX。正好是我们期待的结果。
分析到这里我们再回到原来的题目。我们要做的仅仅是把字符串分成两段,第一段为前面m个字符,其余的字符分到第二段。再定义一个翻转字符串的函数,按照前面的步骤翻转三次就行了。时间复杂度和空间复杂度都合乎要求。
#include "string.h"
// Move the first n chars in a string to its end
char* LeftRotateString(char* pStr, unsigned int n)
{
if(pStr != NULL)
{
int nLength = static_cast(strlen(pStr));
if(nLength > 0 || n == 0 || n > nLength)
{
char* pFirstStart = pStr;
char* pFirstEnd = pStr + n - 1;
char* pSecondStart = pStr + n;
char* pSecondEnd = pStr + nLength - 1;
// reverse the first part of the string
ReverseString(pFirstStart, pFirstEnd);
// reverse the second part of the strint
ReverseString(pSecondStart, pSecondEnd);
// reverse the whole string
ReverseString(pFirstStart, pSecondEnd);
}
}
return pStr;
}
// Reverse the string between pStart and pEnd
void ReverseString(char* pStart, char* pEnd)
{
if(pStart == NULL || pEnd == NULL)
{
while(pStart <= pEnd)
{
char temp = *pStart;
*pStart = *pEnd;
*pEnd = temp;
pStart ++;
pEnd --;
}
}
}
以abcdef为例
另外一种思路:
abc defghi,要abc移动至最后,可以是这样的过程:abc defghi->def abcghi->def ghiabc
使用俩指针,p1指向ch[0],p2指向ch[m-1],p2每次移动m 的距离,p1 也跟着相应移动,
每次移动过后,交换。如上第一步,交换abc 和def ,就变成了 abcdef->defabc
第一步,
abc defghi->def abcghi
第二步,继续交换,
def abcghi->def ghiabc
整个过程,看起来,就是abc 一步一步 向后移动
abc defghi
def abcghi
def ghi abc
//最后的 复杂度是O(m+n)
再举一个例子,如果123 4567890要变成4567890 123:
123 4567890
1. 456 123 7890
2. 456789 123 0
3. 456789 12 0 3
4. 456789 1 0 23
5. 4567890 123 //最后三步,相当于0前移,p1已经不动。
27.跳台阶问题
题目:一个台阶总共有n 级,如果一次可以跳1 级,也可以跳2 级。求总共有多少总跳法,并分析算法的时间复杂度。
这道题最近经常出现,包括MicroStrategy 等比较重视算法的公司,都曾先后选用过个这道题作为面试题或者笔试题。
分析:
首先我们考虑最简单的情况。如果只有1级台阶,那显然只有一种跳法。如果有2级台阶,那就有两种跳的方法了:一种是分两次跳,每次跳1级;另外一种就是一次跳2级。
现在我们再来讨论一般情况。我们把n级台阶时的跳法看成是n的函数,记为f(n)。当n>2时,第一次跳的时候就有两种不同的选择:一是第一次只跳1级,此时跳法数目等于后面剩下的n-1级台阶的跳法数目,即为f(n-1);另外一种选择是第一次跳2级,此时跳法数目等于后面剩下的n-2级台阶的跳法数目,即为f(n-2)。因此n级台阶时的不同跳法的总数f(n)=f(n-1)+(f-2)。
我们把上面的分析用一个公式总结如下:
/ 1,n=1
f(n)= 2, n=2
/ f(n-1)+(f-2),n>2
分析到这里,相信很多人都能看出这就是我们熟悉的Fibonacci序列。
int jump_sum(int n) //递归版本
{
assert(n>0);
if (n == 1 || n == 2) return n;
return jump_sum(n-1)+jump_sum(n-2);
}
int jump_sum(int n) //迭代版本
{
assert(n>0);
if (n == 1 || n == 2) return n;
int an, an_1=2, an_2=1;
for (; n>=3; n--)
{
an = an_2 + an_1;
an_2 = an_1;
an_1 = an;
}
return an;
}
28.整数的二进制表示中1 的个数
Traditional question. Use the equation xxxxxx10000 & (xxxxxx10000-1) = xxxxxx00000
Note: for negative numbers, this also hold, even with 100000000 where the “-1” leading to an underflow.
int countOf1(int n) {
int c=0;
while (n!=0) {
n=n & (n-1);
c++;
}
return c;
}
方案2:
保持x不变,用一个数来探测x的每一位是否为1。这种方法比较通用。首先x和1做与运算,判断i的最低位是不是为1。接着把1左移一位得到2,再和x做与运算,就能判断x的次高位是不是1……,这样反复左移,每次都能判断x的其中一位是不是1。
int NumberOf1_Solution2(int i)
{
int count = 0;
unsigned int flag = 1;
while(flag)
{
if(i & flag)
count ++;
flag = flag << 1;
}
return count;
}
可以看出方案1的时间复杂恰好为1的个数,方案2 的时间复杂度为int类型占的位数,即32,方案1的时间复杂度更优越。
29.栈的push、pop 序列
题目:输入两个整数序列。其中一个序列表示栈的push 顺序,
判断另一个序列有没有可能是对应的pop 顺序。
为了简单起见,我们假设push 序列的任意两个整数都是不相等的。
比如输入的push 序列是1、2、3、4、5,那么4、5、3、2、1 就有可能是一个pop 系列。
因为可以有如下的push 和pop 序列:
push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,
这样得到的pop 序列就是4、5、3、2、1。
但序列4、3、5、1、2 就不可能是push 序列1、2、3、4、5 的pop 序列。
ANSWER
This seems interesting. However, a quite straightforward and promising way is to actually build the stack and check whether the pop action can be achieved.
如果我们希望pop的数字正好是栈顶数字,直接pop出栈即可;如果希望pop的数字目前不在栈顶,我们就到push序列中还没有被push到栈里的数字中去搜索这个数字,并把在它之前的所有数字都push进栈。如果所有的数字都被push进栈仍然没有找到这个数字,表明该序列不可能是一个pop序列。
int isPopSeries(int push[], int pop[], int n) {
stack helper;
int i1=0, i2=0;
while (i2 < n) {
//pop的数字不在栈顶
while (stack.empty() || stack.peek() != pop[i2])
if (i1
30.在从1 到n 的正数中1 出现的次数
ANSWER
This is complicated... I hate it...
Suppose we have N=ABCDEFG.
if G<1, # of 1’s in the units digits is ABCDEF, else ABCDEF+1
if F<1, # of 1’s in the digit of tens is (ABCDE)*10, else if F==1: (ABCDE)*10+G+1, else (ABCDE+1)*10
if E<1, # of 1’s in 3rd digit is (ABCD)*100, else if E==1: (ABCD)*100+FG+1, else (ABCD+1)*100
… so on.
if A=1, # of 1 in this digit is BCDEFG+1, else it’s 1*1000000;
so to fast access the digits and helper numbers, we need to build the fast access table of prefixes and suffixes.
int countOf1s(int n) {
int prefix[10], suffix[10], digits[10]; //10 is enough for 32bit integers
int i=0;
int base = 1;
while (base < n) {
suffix[i] = n % base;
digit[i] = (n % (base * 10)) - suffix[i];
prefix[i] = (n - suffix[i] - digit[i]*base)/10;
i++, base*=10;
}
int count = 0;
base = 1;
for (int j=0; j
方案2:
我们每次判断整数的个位数字是不是1。如果这个数字大于10,除以10之后再判断个位数字是不是1。基于这个思路,不难写出如下的代码:
int NumberOf1(unsigned int n);
int NumberOf1BeforeBetween1AndN_Solution1(unsigned int n)
{
int number = 0;
// Find the number of 1 in each integer between 1 and n
for(unsigned int i = 1; i <= n; ++ i)
number += NumberOf1(i);
return number;
}
int NumberOf1(unsigned int n)
{
int number = 0;
while(n)
{
if(n % 10 == 1)
number ++;
n = n / 10;
}
return number;
}
这个思路比较简单,易于理解。当然有一个非常明显的缺点就是每个数字都要计算1在该数字中出现的次数,因此时间复杂度是O(n)。当输入的n非常大的时候,需要大量的计算,运算效率很低。
32题
有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b 中的元素,使[序列a 元素的和]与[序列b 元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];
ANSWER
If only one swap can be taken, it is a O(n^2) searching problem, which can be reduced to O(nlogn) by sorting the arrays and doing binary search.
If any times of swaps can be performed, this is a double combinatorial problem.
In the book <
求解思路2:
当前数组a和数组b的和之差为
A = sum(a) - sum(b)
a的第i个元素和b的第j个元素交换后,a和b的和之差为
A' = sum(a) - a[i] + b[j] - (sum(b) - b[j] + a[i])
= sum(a) - sum(b) - 2 (a[i] - b[j])
= A - 2 (a[i] - b[j])
设x = a[i] - b[j]
|A| - |A'| = |A| - |A-2x|
假设A > 0,
当x 在 (0,A)之间时,做这样的交换才能使得交换后的a和b的和之差变小,
x越接近A/2效果越好,
如果找不到在(0,A)之间的x,则当前的a和b就是答案。
所以算法大概如下:
在a和b中寻找使得x在(0,A)之间并且最接近A/2的i和j,交换相应的i和j元素,
重新计算A后,重复前面的步骤直至找不到(0,A)之间的x为止。
求解思路3:
/////////////////////////////////////////
算法
1. 将两序列合并为一个序列,并排序,为序列Source
2. 拿出最大元素Big,次大的元素Small
3. 在余下的序列S[:-2]进行平分,得到序列max,min
4. 将Small加到max序列,将Big加大min序列,重新计算新序列和,和大的为max,小的为min。
////////////////////////////////////////////////
def mean( sorted_list ):
if not sorted_list:
return (([],[]))
big = sorted_list[-1]
small = sorted_list[-2]
big_list, small_list = mean(sorted_list[:-2])
big_list.append(small)
small_list.append(big)
big_list_sum = sum(big_list)
small_list_sum = sum(small_list)
if big_list_sum > small_list_sum:
return ( (big_list, small_list))
else:
return (( small_list, big_list))
tests = [ [1,2,3,4,5,6,700,800],
[10001,10000,100,90,50,1],
range(1, 11),
[12312, 12311, 232, 210, 30, 29, 3, 2, 1, 1]
]
for l in tests:
l.sort()
print
print "Source List:/t", l
l1,l2 = mean(l)
print "Result List:/t", l1, l2
print "Distance:/t", abs(sum(l1)-sum(l2))
print '-*'*40
输出结果
Source List: [1, 2, 3, 4, 5, 6, 700, 800]
Result List: [1, 4, 5, 800] [2, 3, 6, 700]
Distance: 99
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
Source List: [1, 50, 90, 100, 10000, 10001]
Result List: [50, 90, 10000] [1, 100, 10001]
Distance: 38
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
Source List: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Result List: [2, 3, 6, 7, 10] [1, 4, 5, 8, 9]
Distance: 1
-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
Source List: [1, 1, 2, 3, 29, 30, 210, 232, 12311, 12312]
Result List: [1, 3, 29, 232, 12311] [1, 2, 30, 210, 12312]
Distance: 21