1.画图让抽象问题形象化
图像能够使抽象的问题具体化,形象化,通过画图能够找到问题的规律,以及题目隐含特点,以便我们能够找出题目的边界条件。
例题:二叉树的镜像
为了理解题意,可以画出一棵二叉树和它的镜像图。不难发现,两棵树的根节点相同,但他们的左右两个子节点交换了位置。
因此我们可以得出如下转换的过程图:
总结一下,我们得出一棵树的镜像的过程:先前序遍历这棵树的每个节点,如果遍历到的节点有子节点,就交换它们的两个子节点,当交换完所有的非叶子节点的左右子节点之后,就得到了树的镜像。
//树的镜像
struct BinaryTreeNode
{
int data;
BinaryTreeNode* _left;
BinaryTreeNode* _right;
};
void Mirror(BinaryTreeNode* pNode)
{
if (pNode == NULL)
return;
if (pNode->_left == NULL&&pNode->_right == NULL)
return;
BinaryTreeNode* tem = pNode->_left;
pNode->_left = pNode->_right;
pNode->_right = tem;
if (pNode->_left != NULL)
Mirror(pNode->_left);
if (pNode->_right != NULL)
Mirror(pNode->_right);
}
2.举例让抽象问题具体化
当一眼看不出问题中的规律时,试着有一两个实例来模拟操作的过程,可帮助我们找寻规律,理清算法思路。
例题:二叉树的层序遍历
从上往下按照每一次从左到右打印节点的值
从上往下打印的序列为8、6、10、5、7、9、12
通过分析具体的例子可以得出,每打印一个节点的的时候,如果该节点有有叶子节点,则把该节点的子节点放入到一个队列的尾部。接下来从队列的头取出最早进入队列的节点,重复前面的打印操作,直到队列中所有的节点都被打印出来为止。
//层序遍历二叉树
struct BinaryTreeNode
{
int data;
BinaryTreeNode* left;
BinaryTreeNode* right;
};
void LevelOrder(BinaryTreeNode* pRoot)
{
if (pRoot == NULL)
return;
queue<BinaryTreeNode*>q;
q.push(pRoot);
while (!q.empty())
{
BinaryTreeNode* front = q.front();
q.pop();
cout << front->data << " ";
if (front->left != NULL)
q.push(front->left);
if (front->right != NULL)
q.push(front->right);
}
}
举一反三:如果要求处理一棵二叉树的遍历序列,可以先找到二叉树的根节点,在基于根节点把整棵树的遍历序列拆分为左子树和右子树所对应的序列,再递归处理这两个序列。
3.分解让复杂问题简单化
把大的问题分解后的各个小问题分别解决,然后把小问题的解决方案结合起来解决大问题。
例题:复杂链表的复制
O(n)时间复杂的解法
第一步:根据原链表的每个节点创建对应的新节点。
第二步:根据原节点的任意指针来修改新节点的任意指针。
第三步:分离链表,生成新的链表。
//复杂链表的复制
struct ComplexListNode
{
int data;
ComplexListNode* _next;
ComplexListNode* sibling;
};
void CopyNode(ComplexListNode* head)
{
ComplexListNode* cur = head;
while (cur != NULL)
{
ComplexListNode* newNode = new ComplexListNode();
newNode->data = cur->data;
newNode->_next = cur->_next;
newNode->sibling = NULL;
cur->_next = newNode;
cur = newNode->_next;
}
}
void ConnectSibling(ComplexListNode* head)
{
ComplexListNode* cur = head;
while (cur != NULL)
{
ComplexListNode* pNext = cur->_next;
if (cur->sibling != NULL)
{
pNext->sibling = cur->sibling->_next;
}
cur = pNext->_next;
}
}
ComplexListNode* GetNewHead(ComplexListNode* head)
{
ComplexListNode* cur = head;
ComplexListNode* newHead = NULL;
ComplexListNode* tem = NULL;
if (cur != NULL)
{
newHead = tem = cur->_next;
cur->_next = tem->_next;
cur = cur->_next;
}
while (cur != NULL)
{
tem->_next = cur->_next;
tem = tem->_next;
cur->_next = tem->_next;
cur = cur->_next;
}
return newHead;
}
ComplexListNode* Copy(ComplexListNode* head)
{
CopyNode(head);
ConnectSibling(head);
return GetNewHead(head);
}
4.总结:当我们遇到难题是时,画图、举例和分解三种办法能够帮助我们解决复杂问题。
画图能使抽象的问题形象化。问题涉及链表、二叉树等数据结构时,如果画出草图,题目中隐藏的规律就有可能变得直观。
一两个例子能使抽象的问题具体化。很多算法相关的问题都很抽象,未必一眼能够看出规律。这时举例子,一步一步模拟运行的过程,就有可能找出解决问题的方法。
把复杂问题分解成若干个小问题,是解决很多复杂问题的有效方法。遇到的问题过大,可以尝试先把大问题分解陈小问题,然后在递归解决小问题。分治法、动态规划等都是应用分解复杂问题的思路。