数据结构面试题

1. 如何判断两棵树是否相同,写出代码

对于树,经常采用递归调用

bool IsSameTree(TreeNode *tree1, TreeNode *tree2)
{
	bool sign1;
	bool sign2;
	sign1 = (NULL == tree1);
	sign2 = (NULL == tree2);

	/* 两棵树都为空 */
	if (sign1 & sign2)
	{
		return true;
	}

	/* 两棵树都不为空 */
	if (false == sign1 && false == sign2)
	{
		if (tree1 ->val == tree2 ->val)
		{
			return IsSameTree(tree1 ->left, tree2 ->left) & IsSameTree(tree1 ->right, tree2 ->right);
		}
		return false;
	}

	/* 一颗为空,一颗不为空 */
	else
	{
		return false;
	}
}
2. 如何获得链表中倒数第K个元素,写出代码,不能利用API

    利用STL中的栈适配器,先将所有元素入栈,然后删除栈顶的K - 1个元素,然后返回栈顶元素

int GetValue(ListNode *a, int k)
{
	stack<int> intStack;
	int i;
	int result;
	assert(NULL != a);
	do
	{
		intStack.push(a->val);
		a = a->next;
	}while (a);

	for (i = 0; i < k - 1; i++)
	{
		intStack.pop();
	}
	result = intStack.top();

	return result;
}
3. 把二元查找树转换成排序的双向链表,二元查找树的定义如下:

structBSTreeNode // a node in the binary search tree  
    {  
        int         m_nValue; // value of node  
        BSTreeNode  *m_pLeft;  // left child of node  
        BSTreeNode  *m_pRight; // right child of node  
    };  

// Covert a sub binary-search-tree into a sorted double-linked list  
// Input: pNode -           the head of the sub tree  
//        pLastNodeInList - the tail of the double-linked list  
void ConvertNode(BSTreeNode* pNode, BSTreeNode*& pLastNodeInList)  
{  
      if(pNode == NULL)  
            return;  
                                                  
      BSTreeNode *pCurrent = pNode;  
                                                  
      // Convert the left sub-tree  
      if (pCurrent->m_pLeft != NULL)  
            ConvertNode(pCurrent->m_pLeft, pLastNodeInList);  
                                                  
      // Put the current node into the double-linked list  
      pCurrent->m_pLeft = pLastNodeInList;  
      if(pLastNodeInList != NULL)  
            pLastNodeInList->m_pRight = pCurrent;  
                                                  
      pLastNodeInList = pCurrent;  
                                                  
      // Convert the right sub-tree  
      if (pCurrent->m_pRight != NULL)  
            ConvertNode(pCurrent->m_pRight, pLastNodeInList);  
}  
                                                  
// Covert a binary search tree into a sorted double-linked list  
// Input: pHeadOfTree - the head of tree  
// Output: the head of sorted double-linked list  
BSTreeNode* Convert_Solution1(BSTreeNode* pHeadOfTree)  
{  
      BSTreeNode *pLastNodeInList = NULL;  
      ConvertNode(pHeadOfTree, pLastNodeInList);  
                                                  
      // Get the head of the double-linked list  
      BSTreeNode *pHeadOfList = pLastNodeInList;  
      while(pHeadOfList && pHeadOfList->m_pLeft)  
            pHeadOfList = pHeadOfList->m_pLeft;  
                                                  
      return pHeadOfList;  
}  

4. 设计一个包含min函数的栈

    在原来栈的基础上,另设一个变量用来保存min,push时候 如果 v_push >= min, v_push 直接入栈, 如果 v_push < min, 那么入栈的是 2 * v_push - min, 然后 min = v_push. 出栈时, 如果栈顶的top >= min 直接出,如果 top < min 则出现异常,将min作为pop的返回值,另外需要还原前一个最小值,方法是 min = 2 * min - top

5. 求子数组的最大和

    输入一个整形数组,里面有整数也有负数,数组中的连续一个或多个整数组成一个数组,称为子数组,求子数组的最大和,要求时间复杂度为O(N)

    分析:可以采用DP的思想:从第一个元素开始累加,每次累加一个与已经保存的最大和比较,如果大于或者等于最大和,则继续进行;如果小于最大和,这将当前技术归0,然后继续累加。

int GetMaxSumOfSubArray(int a[], int length)
{
	assert(NULL != a);
	assert(0 < length);

	int MaxSum = 0;
	int CurrentSum = 0;

	for (int i = 0; i < length; i++)
	{
		CurrentSum += a[i];
		if (MaxSum <= CurrentSum)
		{
			MaxSum = CurrentSum;
		}
		if (0 > CurrentSum)
		{
			CurrentSum = 0;
		}
	}
	
	/* 如果数组里面的元素都为负数,则输出里面的最大元素 */
	if (0 > MaxSum)
	{
		MaxSum = a[0];
		for (int j = 1; j < length; j++)
		{
			if (MaxSum < a[j])
			{
				MaxSum = a[j];
			}
		}
	}
	return MaxSum;
}
6. 给定一个二元树,打印和为给定值的所有路径

二元树的结构定义如下:

struct BinaryTreeNode
{
     int m_nValue;
     BinaryTreeNode * m_pLeft;
     BinaryTreeNode * m_pRight;
};

输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如输入整数22 和如下二元树
          10
        / \
       5  12
      / \
     4   7
则打印出两条路径:10, 12 和10, 5, 7。

思路:

1、当访问到某一节点时,把该结点的值添加到当前和变量,且把该结点压入栈中。

2、若结点为叶子结点,且当前和变量==期望的和,则打印栈中的结点值,即为所需的路径。

3、若结点不是叶子结点,继续访问它的左孩子结点,访问它的右孩子结点。

4、删除该结点。包括从当前和变量中减去结点值,从栈中弹出结点值。此时,已回到父结点。

程序中的栈,利用STL中的vector,这样简化了编码工作。

具体的findPath函数代码如下:

void findPath(BinaryTreeNode *pTreeNode,
              int expectedSum,
              vector<int>& path,
              int & currentSum)
 {
     if( !pTreeNode )
         return;
     currentSum += pTreeNode->m_nValue;
     path.push_back(pTreeNode->m_nValue);
     bool isLeaf = !(pTreeNode->m_pLeft) && !(pTreeNode->m_pRight);
     if(currentSum == expectedSum && isLeaf)
     {   
         vector<int>::iterator iter;
         for(iter = path.begin(); iter != path.end(); iter++)
         {   
             cout << *iter << "\t";
         }   
         cout << endl;
     }   
     if(pTreeNode->m_pLeft)
         findPath(pTreeNode->m_pLeft, expectedSum, path, currentSum);
     if(pTreeNode->m_pRight)
         findPath(pTreeNode->m_pRight, expectedSum, path, currentSum);
 
     currentSum -= pTreeNode->m_nValue;
     path.pop_back();
 }

7. 查找数组中的最小的K个元素。

    方法:利用STL里的大根堆实现

typedef multiset<int, greater<int> > max_heap;
void find_k_least(const vector<int>& data, max_heap& heap, int k)
{
	if (0 > k)
	{
		return;
	}
	vector<int>::const_iterator itea;
	for (itea = data.begin(); itea != data.end(); itea++)
	{
		if (heap.size() < k)
		{
			heap.insert(*itea);   //填堆
		}
		else
		{
			max_heap::iterator it_first = heap.begin();
			if (*itea < *it_first)
			{
				heap.erase(*it_first);   //如果遇到比根小的元素,将根替换成新元素
				heap.insert(*itea);
			}
		}
	}
}

8. 根据上排给出十个数,在下排填出对应的十个数

给你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次....   
以此类推..  

解题思路:关键是理解“要求下排每个数都是先前上排那十个数在下排出现的次数”。

做以下分析:设总共有n个数,上排a[0...n-1],下排b[0...n-1],。

1)下排n个数的累加和为n,即b[0]+b[1]+...+b[n-1] = n

2)ai*bi的累加和也为n,即a[0]*b[0]+a[1]*b[1]+...+a[n-1]*b[n-1] = n

3)对于b中任意一个元素b[j], 都存在i,a[i] = b[j].

4)对于b中任意一个元素b[j],都有b[j] >= 0

5)如果a中存在负数。其在b中出现的次数一定为0. 如果a中数值大于n,则其出现次数也为0.

6)a中至少有两个非0数值在b中出现的次数非0

a:由1)n > n*b[i],其中b[i]为最小值,则a b中一定均有数值0,否则无解。设a[0] = 0,b[0]为a[0]在b中出现次数。

b:由于b中一定存在0,则0的出现次数一定大于0,因此b[0]>0 且b[0] < n,b[1...n-1]中至少一个值为0. 非0元素出现的次数一共是n-b[0].

c:有2)和6)对任意a[i],a[i]*b[i] < n,即b[i] < n/a[i],对所有a[i]>=n/2的元素中,在b中出现的次数必须最多只有1个出现次数不为0,且为1.其余出现次数均为0,即[1, n/2)范围内最多只有n/2-1个元素,故0出现的次数必不小于n/2, [n/2,n)范围内的元素必有一个出现次数为1。因此a数列中也必须有1,否则无解。

d:有c得在数值范围为(0,n/2)中(假设有x这样的数)出现的次数和s为n - b[0]或n-b[0]-1。其中1出现的次数至少为1(由c得)。又如果1出现的次数为1,则1出现的次数已经为2,故1出现的次数必大于1.设为x,则x出现的次数至少为1,而x>1,如果x出现的次数大于1,那么必须要有其他数出现的次数为x,这样无法收敛。故x出现的次数只能为1,1出现的次数只能为2.

结论:

0出现的次数为n-4,1出现的次数为2.2出现的次数为1。n-4出现的次数为1.如果数列中无则四个数,无解。

9. 判断两个链表是否相交

    与判断链表是否有环的方法一样

10. 找出重复数字

    假设你有一个用1001个整数组成的数组,这些数字是任意排列的。但是他们的范围是1--1000,并且除了一个数字出现两次外,其余都出现了一次,找出那个重复的数字

    方法一:1---1000加起来有个总和,那后数组里的数加起来有个总和,两者只差即为那个重复的数

int FindRepetNum(int a[], int n)
{
	if (NULL == a || 1 >= n)
	{
		return 0;
	}
	int sum1 = 0;
	int sum2 = 0;
	int i;
	for (i = 0; i < n; i++)
	{
		sum1 += a[i];
	}
	for (i = 1; i < n; i++)
	{
		sum2 += i;
	}
	/* 两者差值即为重复的那个数 */
	return sum1 - sum2;
}
    方法二:利用hash_map的方法

int FindRepetNum(int a[], int n)
{
	if (NULL == a || 1 >= n)
	{
		return 0;
	}
    hash_map<int ,int> HashBarrel;
	int i;
	for (i = 0; i < n; i++)
	{
		if (!HashBarrel.count(a[i]))
		{
			HashBarrel[a[i]] = i;
		}
		else
		{
			return a[i];
		}
	}
}

11. 求二叉树中两个节点的最大距离

如果把二叉树看成一个图,父子节点之间的连线看成是双向的,我们姑且定义"距离"为两个节点之间的个数。

写一个程序求一棵二叉树中相距最远的两个节点之间的距离。

如下图所示,粗箭头的边表示最长距离:


树中相距最远的两个节点是A, B

分析可知:对于二叉树,若要两个节点U,V相距最远,有两种情况:

1,从U节点到V节点之间的路径经过根节点

2,从U节点到V节点之间的路径不经过根节点,这种情况下,U,V节点必定在根节点的左子树或者右子树上,这样就转化为求以根节点的孩子节点为根节点的二叉树中最远的两个节点间的距离

上面所说的经过根节点,是指路径中包含根节点,例如:加入上图中只有左子树FGHA, 那么最长距离的两个节点是F, A,该路径中包含根节点F,也称为经过根节点。

于是我们可以递归求解,按照二叉树的中序遍历方式遍历二叉树,在遍历的过程中寻找相距最远的两个节点。

程序描述如下:

typedef struct Node {  
    struct Node *pleft;     //左孩子  
    struct Node *pright;    //右孩子  
    char chValue;           //该节点的值  
  
    int leftMaxValue;       //左子树最长距离  
    int rightMaxValue;      //右子树最长距离  
}LNode, *BinTree;  
  
void findMaxLen(BinTree root, int *maxLen) {  
    //遍历到叶子结点,返回  
    if(root == NULL)  
        return;  
  
    //如果左子树为空,那么该节点左边最长距离为0  
    if(root->pleft == NULL)  
        root->leftMaxValue = 0;  
  
    //如果右子树为空,那么该节点右边最长距离为0  
    if(root->pright == NULL)  
        root->rightMaxValue = 0;  
  
    //如果左子树不为空,递归寻找左子树最长距离  
    if(root->pleft != NULL)  
        findMaxLen(root->pleft, maxLen);  
  
    //如果右子树不为空,递归寻找右子树最长距离  
    if(root->pright != NULL)  
        findMaxLen(root->pright, maxLen);  
  
    //计算左子树中距离根节点的最长距离  
    if(root->pleft != NULL) {  
        if(root->pleft->leftMaxValue > root->pleft->rightMaxValue)  
            root->leftMaxValue = root->pleft->leftMaxValue + 1;  
        else  
            root->leftMaxValue = root->pleft->rightMaxValue + 1;  
    }  
  
    //计算右子树中距离根节点的最长距离  
    if(root->pright != NULL) {  
        if(root->pright->leftMaxValue > root->pright->rightMaxValue)  
            root->rightMaxValue = root->pright->leftMaxValue + 1;  
        else  
            root->rightMaxValue = root->pright->rightMaxValue + 1;  
    }  
  
    //更新最长距离  
    if(root->leftMaxValue + root->rightMaxValue > *maxLen)  
        *maxLen = root->leftMaxValue + root->rightMaxValue;  
}  
12. 求1+2+3+4···+n,不能使用for,while,switch等关键字

方法一:利用递归

int add_fun(int n, int &sum)  
{  
    n && add_fun(n-1, sum);  
    return (sum+=n);  
}  
方法二:利用函数指针

typedef int (*fun)(int);  
  
int solution_f1(int i)  
{  
    return 0;  
}  
  
int solution_f2(int i)  
{  
    fun f[2]={solution_f1, solution_f2};  
    return i+f[!!i](i-1);  
}
13. 输出单链表的倒数第k个元素,链表的倒数第0个节点为链表的尾指针

方法一:利用C++里的栈容器

int Reverse(ListNode *a, int k)
{
	stack<int> ListStack;
	int result;
	if (NULL == a)
	{
		return NULL;
	}
	/* 所有元素入栈 */
	while (a)
	{
		ListStack.push(a->val);
		a = a->next;
	}
	/* 将前k个元素出栈 */
	for (int i = 0; i <= k - 1; i++)
	{
		ListStack.pop();
	}
	result = ListStack.top();
	return result;
}
14. 输入一个已经按升序排列过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数。要求时间复杂度是O(n)。如果有多对数字的和等于输入数字,则输出任意一组即可

方法一:利用哈希映射的方法

vector<int> FindTwoNum(int a[], int n, int sum)
{
	assert(NULL != a);
	assert(n >= 2);
	vector<int> Vec;
	vector<int>::iterator itea;
	hash_map<int ,int> HashTemp;
	int i;
	for (i = 0; i < n; i++)
	{
		/* 不存在,就存到HashTemp */
		if (!HashTemp.count(sum - a[i]))
		{
			HashTemp[a[i]] = i;
		}
		else
		{
			Vec.push_back(a[i]);
			Vec.push_back(sum - a[i]);
			break;
		}
	}
	return Vec;
}
方法二:利用数组的有序性,分别从头和尾相加,如果和大于sum,则尾向前移动;如果和小于sum,则头向后移动;如果和等于sum,则退出

vector<int> FindTwoNum(int a[], int n, int sum)
{
	assert(NULL != a);
	assert(n >= 2);
	vector<int> Vec;
    int little = 0;
	int big = n - 1;
	while (little != big)
	{
		if (a[little] + a[big] > sum)
		{
			big--;
			continue;
		}
		else if (a[little] + a[big] < sum)
		{
			little++;
		}
		else
		{
			Vec.push_back(a[little]);
			Vec.push_back(a[big]);
			break;
		}
	}
	return Vec;
}

15. 输入一颗二元树,从上到下,从左到右打印节点的值

这道题实质上是要求遍历一棵二元树,只不过不是我们熟悉的前序、中序或者后序遍历。
我们从树的根结点开始分析。自然先应该打印根结点8,同时为了下次能够打印8的两个子结点,我们应该在遍历到8时把子结点6和10保存到一个数据容器中。现在数据容器中就有两个元素6 和10了。按照从左往右的要求,我们先取出6访问。打印6的同时要把6的两个子结点5和7放入数据容器中,此时数据容器中有三个元素10、5和7。接下来我们应该从数据容器中取出结点10访问了。注意10比5和7先放入容器,此时又比5和7先取出,就是我们通常说的先入先出。因此不难看出这个数据容器的类型应该是个队列。
既然已经确定数据容器是一个队列,现在的问题变成怎么实现队列了。实际上我们无需自己动手实现一个,因为STL已经为我们实现了一个很好的deque(两端都可以进出的队列),我们只需要拿过来用就可以了。
我们知道树是图的一种特殊退化形式。同时如果对图的深度优先遍历和广度优先遍历有比较深刻的理解,将不难看出这种遍历方式实际上是一种广度优先遍历。因此这道题的本质是在二元树上实现广度优先遍历
struct BinTree
{
	int val;
	BinTree *left;
	BinTree *right;
};

void PrintFromTopToBottom(BinTree *root)
{
	if (NULL == root)
	{
		return;
	}
	deque<BinTree*> Tree;
	Tree.push_front(root);
	while (Tree.size())
	{
		BinTree *node = Tree.front();
		Tree.pop_front();
		cout << node->val << ' ';
		if (node->left)
		{
			Tree.push_front(node->left);
		}
		if (node->right)
		{
			Tree.push_front(node->right);
		}
	}
}














  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值