几道数据结构和算法题的回顾总结

这周做了一些牛客网上剑指offer的题.选取几道个人觉得比较典型,有意思的题拿出来进行分析.

1.链表中环的入口节点
一个链表中包含一个环,请找出这个环的入口节点.
分析:一般答案不那么显然的链表题,大多都可以用这种方法解决:派出两个指针干活.这道题也是如此,具体是派出快慢2个指针同时出发,快指针一次走两步,慢指针一次走一步,找出他们第一次相遇的位置,然后就比较容易操作了,之后就是这是一个小学数学的行程问题了,这里就不多赘诉,指针的题需要注意下临界条件,这道题还好.

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* head){
        if(head == NULL || head->next ==NULL || head->next->next == NULL)
            return NULL;
            //派出快慢两个指针,进行第一次相遇
        ListNode *slow = head->next;
        ListNode *fast = head->next->next;
        while(slow != fast){
            if (fast->next->next != NULL){
                slow = slow->next;
                fast = fast->next->next;
            }//判断是否是环,其实如果题目出的正确的话可以不用这个条件判断
            else
                return NULL;
        }
        //指针相遇后重新派出两个速度一样的指针再度相遇,相遇后的位置便是环的入口
        ListNode *p1 = head;
        ListNode *p2 = slow;
        while (p1 != p2){
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

2.删除链表中重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,结果返回链表头指针。
分析:乍看之下是一道思路非常明确很温柔的题,实际上这道题坑了我很久才弄出来,说不定有更简单的思路(不包括重新建一个链表哈,只能在原始链表上操作),不过我暂时懒得去看答案了,心累…具体思路也是需要排出两个指针,前锋负责探路找出重复的节点,殿后的就负责把删除节点后的链表重新拼接起来,同时还需要设定一个判断是否是重复节点bool值…还是直接上程序吧,这道题的细节操作和临界处理上都花了我不少时间,很难语言描述清楚.

class Solution {
public:
    ListNode* deleteDuplication(ListNode* head){
        if (head == NULL|| head->next == NULL)
            return head;
        //在链表表头加了一个新的head,为了处理临界情况
        ListNode* headNew = new ListNode(INT_MIN);
        headNew->next = head;
        ListNode* p1 = headNew;
        ListNode* p2 = headNew->next;
        bool isSame = false;
        //核心操作步奏
        while(p2->next != NULL){
            if((p2->val != p2->next->val) && (isSame == false)){
                p1 = p1->next;
                p2 = p2->next;
            }
            else if (p2->val == p2->next->val){
                p2 = p2->next;
                isSame = true;
            }
            else{
                p2 = p2->next;
                p1->next = p2;
                isSame = false;
            }
        }
        //收尾的临界情况,漏了也会报错
        if (isSame == true)
            p1->next = NULL;
        return headNew->next;
    }
};

补充.后来看了下人家的代码,用的是递归,递归就简单多了,不管是思想上还是代码上都更简洁,下面给出递归的代码:

class Solution {
public:
    ListNode* deleteDuplication(ListNode* head){
        if(head == NULL || head->next == NULL)
            return head;
        ListNode *p = head;
        if(p->next->val == p->val){
            p = p->next->next;
            while(p && p->val == head->val)
                p = p->next;
            return deleteDuplication(p);
        }
        head->next = deleteDuplication(p->next);
        return head;
    }
};

3.二叉树的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析: 1.若该节点包含右子树,那么它的下一个节点必然是这个右子树中的最左边的节点.2.若该节点无右子树,那么它的下一个节点只能是它的父节点或往上的节点.这时候又要分类:如果它正好是它父节点的左子节点,那么它的父节点就是它的下一个节点,如果不是,那么只能继续向上回溯.

class Solution {
public:
    TreeLinkNode* GetleftNode(TreeLinkNode* Node){
        if (Node->left == NULL)
            return Node;
        else
            return GetleftNode(Node->left);
    }

    TreeLinkNode* GetRightDadNode(TreeLinkNode* Node){
        if (Node->next == NULL)
            return NULL;
        if (Node == Node->next->left)
            return Node->next;
        else
            return GetRightDadNode(Node->next);
    }
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == NULL)
            return pNode;
        if (pNode->right != NULL){
            return GetleftNode(pNode->right);
        }
        else{
            return GetRightDadNode(pNode);
        }
    }
};

4.序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树
分析:这题采用的是前序遍历,对于空节点处用0xFFFFFFFF进行补足,思想是非常简单的递归,但是反序列化的具体操作却不是那么容易,个人认为是二叉树的递归思想中难度最高的题之一了.这道题是看的答案,这里盗一波人家的代码.

class Solution {
public:
    vector<int> buf;
    void dfs1(TreeNode *root) {
        if(!root) buf.push_back(0xFFFFFFFF);
        else {
            buf.push_back(root->val);
            dfs1(root->left);
            dfs1(root->right);
        }
    }
    TreeNode* dfs2(int* &p) {
        if(*p==0xFFFFFFFF) {
            p++;
            return NULL;
        }
        TreeNode* res =new TreeNode(*p);
        p++;
        res->left=dfs2(p);
        res->right=dfs2(p);
        return res;
    }
    char* Serialize(TreeNode *root) {
        buf.clear();
        dfs1(root);
        int bufSize=buf.size();
        int *res=new int[bufSize];
        for(int i=0;i<bufSize;i++) res[i]=buf[i];
        return (char*)res;
    }
    TreeNode* Deserialize(char *str) {
        int *p=(int*)str;
        return dfs2(p);
    }
};

5.数据流中的中位数
如何得到一个数据流中的中位数?
分析:输入一个数据流,实时的计算出数据流的中位数.可以分别建立一个大顶堆和小顶堆,使得二者各存一半的数据,且大顶堆中的最大值小于等于小顶堆中的最小值,这样的话中位数始终都是从大顶堆和小顶堆中的top处诞生的,如数据流[1,3,2,4],输入后在大顶堆中就是[2,1],在小顶堆中就是[3,4],所以中位数就是(2+3)/2=2.5.具体操作见程序.为什么要用大顶堆和小顶堆?因为在给定的排序数组中插入数字可以用顶堆的思想达到O(logn)的复杂度.(我怎么感觉二分法也是logn的复杂度,估计是理解不够深刻…)

class Solution {
public:
    priority_queue<int, vector<int>, less<int> > l;
    priority_queue<int, vector<int>, greater<int> > g;
    int k = 0;
    void Insert(int num)
    {
        if (k%2 == 0)
            l.push(num);
        else{
            if(num > l.top())
                g.push(num);
            else{
                int temp = l.top();
                l.pop();
                l.push(num);
                g.push(temp);
            }
        }
        k++;
    }

    double GetMedian()
    {
        if (k==0) return 0;
        if (k == 1) return l.top();
        if (k%2 == 1){
            if (l.top() <= g.top())
                return l.top();
            else 
                return g.top();
        }
        else
            return (float(l.top() + g.top())/2);
    }
};

6.机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
分析:简单来说就是从起点出发,每次递归地向四个方向前进,如果在某个方向遇到停止条件,那么便在这个方向上便停止,直到所有前进的路劲都达到停止条件…

class Solution {
public:
    int bitSum (int qqq){
        int t = 0;
        while(qqq != 0){
            t = t + qqq%10;
            qqq = qqq/10;
        }
        return t;
    }

    int count(int thr, int i, int j, int rows, int cols,vector<vector<int>>& vis){
        if(i<0 || j<0 || i>=rows || j >= cols || vis[i][j] || (bitSum(i)+bitSum(j)> thr))
           return 0;
        else{
            vis[i][j] = 1;
            return(1+count(thr, i+1, j, rows, cols, vis)+
                   count(thr, i, j+1, rows, cols, vis)+
                   count(thr, i-1, j, rows, cols, vis)+
                   count(thr, i, j-1, rows, cols, vis));
        }
    }

    int movingCount(int threshold, int rows, int cols){
        vector<vector<int>> vis(rows, vector<int>(cols, 0));
        int res = count(threshold, 0, 0, rows, cols, vis);
        return res;
    }
};

猜你喜欢

转载自blog.csdn.net/gzt940726/article/details/80556847