链表与二叉树

1. 链表

链表是最基本的数据结构,面试官也常常用链表来考察面试者的基本能力,而且链表相关的操作相对而言比较简单,也适合考察写代码的能力。链表的操作也离不开指针,指针又很容易导致出错。综合多方面的原因,链表题目在面试中占据着很重要的地位。

链表问题中的一个重要的方法叫双指针法。定义两个指针,一个叫慢指针,另一个叫快指针。通常慢指针每次向前移动一个节点,而快指针每次向前移动若干个节点。这个方法通常用于寻找链表中特定的位置。比如找到链表的中点,可以让快指针每次移动两个节点。这样当快指针到达链表末尾时,慢指针刚好在链表中间的位置。

链表结点声明如下:

struct ListNode
{
    int value;
    ListNode * next;
    ListNode(int x) : value(x), next(NULL) {}
};

1.1 求单链表中结点的个数

这是最最基本的了,应该能够迅速写出正确的代码,注意检查链表是否为空。时间复杂度为O(n)。

int GetListLenght(ListNode* head)
{
    if(head == NULL)    return 0;
    ListNode* current = head;
    int len = 0;
    while(current != NULL)
    {
        len++;
        current = current->next;
    }
    return len;
}

1.2 将单链表反转

从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)。

ListNode* ReverseList(ListNode* head)
{
    if(head == NULL || head->next == NULL)    return head;
    ListNode* reverseHead = NULL;
    ListNode* current = head;
    while(current != NULL)
    {
        ListNode* temp = current;
        current = current->next;
        temp->next = reverseHead;
        reverseHead = temp;
    }
    return reverseHead;
}

1.3 查找单链表中的倒数第K个结点(k>0)

使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。

ListNode * RGetKthNode(ListNode * head, unsigned int k)
{
    if(head == NULL)    return NULL;
    ListNode* aHead = head, behind = head;
    while(k>1)
    {
        if(aHead == NULL)    return NULL;
        aHead = aHead->next;
        k--;
    }
    while(aHead->next != NULL)
    {
        behind = behind->next;
        aHead = aHead->next;
    }
    return behind;
}

1.4 判断一个单链表中是否有环

如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。

bool HasCycle(ListNode* head)
{
    ListNode* fast = head,* slow = head;
    while(fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)    return true;
    }
    return false;
}

1.5 判断一个单链表中是否有环,如果存在,求进入环中的第一个节点

这题不光要求出是否有环,而且还需要得到这个环开始的节点。譬如下面这个,起点就是n2。

        n6-----------n5
        |              |
n1------n2----n3----n4|

我们仍然可以使用两个指针fast和slow,fast走两步,slow走一步,判断是否有环,当有环重合之后,譬如上面在n5重合了,那么如何得到n2呢?
首先我们知道,fast每次比slow多走一步,所以重合的时候,fast移动的距离是slot的两倍,我们假设n1到n2距离为a,n2到n5距离为b,n5到n2距离为c,fast走动距离为a+ b+c+b,而slow为 a+b,有方程a+b+c+b=2x(a+b),可以知道a=c,所以我们只需要在重合之后,一个指针从n1,而另一个指针从n5,都每次走一步,那么就可以在n2重合了。

ListNode* detectCycle(ListNode* head)
{
    ListNode *slow = head, *fast = head;
    while(fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            ListNode* slow2 = head;
            while(slow2 != slow)
            {
                slow2 = slow2->next;
                slow = slow->next;
            }
            return slow2;
        }
    }
    return NULL;
}

1.6 合并两个排好序的链表

利用附加头结点简化操作

ListNode* MergeTowList(ListNode* head1, ListNode* head2)
{
    ListNode* dummy(0);
    ListNode* temp = &dummy;
    while(head1 != NULL && head2 != NULL)
    {
        int val1 = head1->value;
        int val2 = head2->value;
        if(val1 < val2)
        {
            temp->next = head1;
            temp = head1;
            head1 = head1->next;
        }
        else
        {
            temp->next = head2;
            temp = head2;
            head2 = head2->next;
        }
    }
    if(head1 != NULL)    temp->next = head1;
    else if(head2 != NULL)    temp->next = head2; 
    return dummy.next;
}

1.7 去除有序链表中的重复元素

ListNode *deleteDuplicates(ListNode *head) {
    if (head == nullptr) return nullptr;
    for (ListNode *prev = head, *cur = head->next; cur; cur = prev->next) {
        if (prev->val == cur->val) {
            prev->next = cur->next;
            delete cur;
        } 
        else {
            prev = cur;
        }
    }
    return head;
}

1. 二叉树

树是一种比较重要的数据结构,尤其是二叉树。二叉树是一种特殊的树,在二叉树中每个节点最多有两个子节点,一般称为左子节点和右子节点(或左孩子和右孩子),并且二叉树的子树有左右之分,其次序不能任意颠倒。二叉树是递归定义的,因此,与二叉树有关的题目基本都可以用递归思想解决,当然有些题目非递归解法也应该掌握,如非递归遍历节点等等。

二叉树节点定义如下:

struct BinaryTreeNode
{
    int value;
    BinaryTreeNode* left;
    BinaryTreeNode* right;
    BinaryTreeNode(int x) : value(x), left(NULL), right(NULL) {}
};

2.1 求二叉树中的结点个数

递归解法:

  • 如果二叉树为空,节点个数为0
  • 如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
int GetNodeNumber(BinaryTreeNode* root)
{
    if(root == NULL)    return 0;
    return GetNodeNumber(root->left) + GetNodeNumber(root->right) + 1;
}

2.2 求二叉树的深度

递归解法:

  • 如果二叉树为空,二叉树的深度为0
  • 如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
int GetDeepth(BinaryTreeNode* root)
{
    if(root == NULL)    return 0;
    int depthLeft = GetDeepth(root->left);
    int depthRight = GetDeepth(root->right);
    return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
}

2.3 前序遍历,中序遍历,后序遍历

前序遍历递归解法:如果二叉树为空,空操作;如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树。中序遍历递归解法:如果二叉树为空,空操作;如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树。后序遍历递归解法:如果二叉树为空,空操作;如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点。

void preOrderTraverse(BinaryTreeNode* root)
{
    if(root == NULL)    return;
    visit(root);
    PreOrderTraverse(root->left); // 前序遍历左子树  
    PreOrderTraverse(root->right); // 前序遍历右子树
}

void InOrderTraverse(BinaryTreeNode* root)
{
    if(root == NULL)    return;
    InOrderTraverse(root->left); // 中序遍历左子树 
    visit(root); // 访问根节点 
    InOrderTraverse(root->right); // 中序遍历右子树  
}

void PostOrderTraverse(BinaryTreeNode * root)  
{  
    if(root == NULL)    return;  
    PostOrderTraverse(root->left); // 后序遍历左子树  
    PostOrderTraverse(root->right); // 后序遍历右子树  
    Visit(root); // 访问根节点  
} 

2.4 层次遍历

相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点,访问,若左子节点或右子节点不为空,将其压入队列。

void LevelTraverse(BinaryTreeNode* root)
{
    if(root == NULL) return ;
    queue<BinaryTreeNode*> que;
    que.push(root);
    while(!que.empty())
    {
        BinaryTreeNode* tempNode = que.front();
        que.pop();
        visit(tempNode);
        if(root->left != NULL)    que.push(tempNode->left);
        if(root->right != NULL)    que.push(tempNode->right);
    }
    return ;
}

2.5 求二叉树第K层的节点个数

递归解法:

  • 如果二叉树为空或者k<1返回0
  • 如果二叉树不为空并且k==1,返回1
  • 如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和
int GetNodeNumKthLevel(BinaryTreeNode* root, int k)
{
    if(root == NULL || k<1)    return 0;
    if(k == 1)    return 1;
    int numLeft = GetNodeNumKthLevel(root->left, k-1);
    int numRight = GetNodeNumKthLevel(root->right,k-1);
    return numLeft + numRight;
}

2.6 求二叉树中叶子节点的个数

递归解法:

  • 如果二叉树为空,返回0;
  • 如果二叉树不为空且左右子树为空,返回1;
  • 如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数。
int GetLeafNodeNum(BinaryTreeNode * root)  
{  
    if(root == NULL)  return 0;  
    if(root->left == NULL && root->right == NULL)  return 1;  
    int numLeft = GetLeafNodeNum(root->left); // 左子树中叶节点的个数  
    int numRight = GetLeafNodeNum(root->right); // 右子树中叶节点的个数  
    return (numLeft + numRight);  
}

2.7 判断两棵二叉树是否结构相同

不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。递归解法如下:

  • 如果两棵二叉树都为空,返回真
  • 如果两棵二叉树一棵为空,另一棵不为空,返回假
  • 如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
bool StructureCmp(BinaryTreeNode * root1, BinaryTreeNode * root2)  
{  
    if(root1 == NULL && root2 == NULL) // 都为空,返回真  
        return true;  
    else if(root1 == NULL || root2 == NULL) // 只有一个为空,返回假  
        return false;  
    bool resultLeft = StructureCmp(root1->left, root2->left); // 比较对应左子树   
    bool resultRight = StructureCmp(root1->right, root2->right); // 比较对应右子树  
    return (resultLeft && resultRight);  
} 

2.8 判断二叉树是不是平衡二叉树

平衡二叉树:
递归解法:

  • 如果二叉树为空,返回真
  • 如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假
bool IsAVL(BinaryTreeNode * root, int & height)  
{  
    if(root == NULL) // 空树,返回真  
    {  
        height = 0;  
        return true;  
    }  
    int heightLeft;  
    bool resultLeft = IsAVL(root->left, heightLeft);  
    int heightRight;  
    bool resultRight = IsAVL(root->right, heightRight);  
    // 左子树和右子树都是AVL,并且高度相差不大于1,返回真  
    if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) 
    {  
        height = max(heightLeft, heightRight) + 1;  
        return true;  
    }  
    else  
    {  
        height = max(heightLeft, heightRight) + 1;  
        return false;  
    }  
}

2.9 求二叉树的镜像

递归解法:

  • 如果二叉树为空,返回空
  • 如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树
BinaryTreeNode* Mirror(BinaryTreeNode* root)
{
    if(root == NULL)    return NULL;
    BinaryTreeNode* mLeft = Mirror(root->left); // 求左子树镜像
    BinaryTreeNode* mRight = Mirror(root->right); // 求右子树镜像
    // 交换左子树和右子树
    root->left = mRight;
    root->right = mLeft;
    return root;
}

2.10 递归的遍历算法

//利用栈进行递归前序遍历
void PreorderTraversal(BinaryTreeNode* root)
{
    stack<const BinaryTreeNode*> s;
    if(root != NULL)    s.push(root);
    while(!s.empty())
    {
        const BinaryTreeNode* temp = s.top();
        s.pop();
        visit(temp);
        if(temp->right != NULL) s.push(temp->right);
        if(temp->left != NULL) s.push(temp->left);
    }
}

void InOrderTraversal(BinaryTreeNode* root)
{
    stack<const BinaryTreeNode*> s;
    const BinaryTreeNode* temp = root;
    while(!s.empty() || temp !=NULL)
    {
        if(temp != NULL){
            s.push(temp);
            temp = temp->left;
        }
        else{
            temp = s.top();
            s.pop();
            visit(temp);
            temp = temp->right;
        }
    }
}

void PostOrderTraversal(BinaryTreeNode* root)
{
    stack<const BinaryTreeNode*> s;
    const BinaryTreeNode* temp = root, * pre = NULL;
    do{
        while(temp != NULL){
            s.push(temp);
            temp = temp->left;
        }
        pre = NULL;
        while(!s.empty()){
            temp = s.top();
            s.pop();
            //如果该结点的右子节点不存在或已被访问,就访问它
            if(temp->right == pre){
                visit(temp);
                pre = temp;
            }
            else{
                s.push(temp);
                temp = temp->right;
                break;
            }
        }
    }while(!s.empty());
}

2.11 判断是否存在一条从root到其中一个叶子节点的路径的和等于给定的值

直接使用深度优先遍历求解

bool DFS(int target, int sum, BinaryTreeNode* root)
{
    if(root == NULL)    return false;
    sum += root->value;
    if(root->left == NULL && root->right == NULL)
    {
        if(sum == target)    return true;
        else return false;
    }
    bool leftPart = DFS(target,sum,root->left);
    bool rightPart = DFS(target,sum,root->right);
    return leftPart || rightPart;
}

bool hasPathSum(BinaryTreeNode* root,int sum)
{
    if(root == NULL)    return false;
    return DFS(sum,0,root);
}

轻松搞定面试中的链表题目
轻松搞定面试中的二叉树题目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,414评论 1 31
  • 四、树与二叉树 1. 二叉树的顺序存储结构 二叉树的顺序存储就是用数组存储二叉树。二叉树的每个结点在顺序存储中都有...
    MinoyJet阅读 1,490评论 0 7
  • 1. 链表 链表是最基本的数据结构,面试官也常常用链表来考察面试者的基本能力,而且链表相关的操作相对而言比较简单,...
    Mr希灵阅读 1,421评论 0 20
  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...
    sunhaiyu阅读 6,411评论 0 14
  • 本文转自 http://www.cnblogs.com/manji/p/4903990.html二叉树-****你...
    doublej_yjj阅读 675评论 0 8