leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除

来源:互联网 发布:python可以建网站吗 编辑:程序博客网 时间:2024/05/05 10:18

这部分考察两个部分:

1、找中点,逆序,部分逆序

2、边界细节处理


1、OJ206单链表全逆序

OJ206代码:

class Solution {public:    ListNode* reverseBetween(ListNode* head, int m, int n) {        if (!head || !m || !n) {            return head;        }                //首先找到第m个节点cur(注意head是第一个节点, 第m个节点是head做m-1次向前)        ListNode *prev = nullptr, *cur = head;        for (int i = m - 1; i > 0; i--) {            prev = cur;            cur = cur->next;        }                //同理, 从第m个节点到第n个节点逆序, 做n-m+1次翻转. 注意一路要确保next非空        ListNode *newed = cur, *newst = cur;        ListNode *next = cur->next;        for (int i = m + 1; next && i <= n; i++) {            ListNode *nn = next->next;            next->next = newst;            newst = next;            next = nn;        }                //这里要注意m=1的情况即从head就翻转, 这样翻转后头节点要变为第n个节点        newed->next = next;        if (prev) {            prev->next = newst;        } else {            head = newst;        }                return head;    }};

2、OJ92 单链表部分翻转

单链表第m到第n个节点逆序,其他的不变

步骤:

1、找到第m个节点,以及它前边的节点prev,注意m = 1时,第m个节点就是head,prev就是nullptr;

2、第m到第n个节点逆序,并记录下新的头节点(原链表第n个节点),第n+1个节点next;新的头指向next;

3、如果prev为空,那么返回新的头;如果不为空,prev指向新的头;

OJ92代码:

class Solution {public:    ListNode* reverseBetween(ListNode* head, int m, int n) {        if (!head || !m || !n) {            return head;        }                //首先找到第m个节点cur(注意head是第一个节点, 第m个节点是head做m-1次向前)        ListNode *prev = nullptr, *cur = head;        for (int i = m - 1; i > 0; i--) {            prev = cur;            cur = cur->next;        }                //同理, 从第m个节点到第n个节点逆序, 做n-m+1次翻转. 注意一路要确保next非空        ListNode *newed = cur, *newst = cur;        ListNode *next = cur->next;        for (int i = m + 1; next && i <= n; i++) {            ListNode *nn = next->next;            next->next = newst;            newst = next;            next = nn;        }                //这里要注意m=1的情况即从head就翻转, 这样翻转后头节点要变为第n个节点        newed->next = next;        if (prev) {            prev->next = newst;        } else {            head = newst;        }                return head;    }};

3、OJ24 swap Nodes in pairs:

对原链表两两翻转,不足两个的不翻转,如原先是1234,翻转后是2143,原先是12345,翻转后是21435

步骤:

1、链表节点数小于2个的不用做直接返回

2、提前记录新的头,原地两两翻转,发现后边不足两个节点时停止

OJ24代码:

class Solution {public:    ListNode* swapPairs(ListNode* head) {        if (!head || !head->next) {            return head;        }                ListNode *cur = head, *next = cur->next, *newhead = next, *prev = nullptr;        while (cur && next) {            ListNode *nn = next->next;            next->next = cur;            cur->next = nn;            if (prev) {                prev->next = next;            }            prev = cur;            cur = nn;            if (cur) {                next = cur->next;            }        }                return newhead;    }};

4、OJ25 reverse Nodes in K-Group

每K个节点逆序,少于k个时不翻转,如原先是12345,k=2,则变为21435,如k=3,则变为32145

OJ25其实是OJ24的变种,也需要提前记录新的头,也需要每次记录本次的新头和新尾,记录上次的尾巴和下次的头;

另外这里的方法是先遍历一次链表获取长度,提前计算出需要翻转多少次

步骤:

1、先遍历一次计算出链表长度len,k大于len的、k=1的情况,不需要翻转,尤其k=1的情况,一定提前滤掉;剩下的就是1<K<=len的情况了

2、提前计算出新的头newhead,每次翻转记录本次新的头,新的尾,下次的起点,让上次的末尾点prevk执行本次新的头,本次新的尾指向下次新的头

3、根据第1步计算出的翻转次数,做相应次数的第2步

4、返回提前计算好的新的头newhead

OJ25代码:

class Solution {public:    ListNode* reverseKGroup(ListNode* head, int k) {        if (!head || k <= 0) {            return head;        }                //首先求链表长度len, 如果发现k大于len则直接返回原链表        int len = 0;        ListNode *st = head;        while (st) {            st = st->next;            ++len;        }        if (k > len || k == 1) {            return head;        }                //如果需要翻转, 计算出需要翻转多少次并翻转, 提前记录新的头prevk, 每次记录新的头prev和尾newtail做新旧连接        st = head;        ListNode *newhead = nullptr, *prevk = nullptr;        int times = len/k;        for (int i = 0; i < times && st; i++) {            ListNode *newtail = st, *prev = nullptr;            int t = k;            while (t) {                ListNode *next = st->next;                st->next = prev;                prev = st;                st = next;                --t;            }            newtail->next = st;            if (!i) {                newhead = prev;            }            if (prevk) {                prevk->next = prev;            }            prevk = newtail;        }                return newhead;    }};

5、OJ61 rotate list

倒数K个节点移到链表前边,如原链表是12345,给定k=2,则结果为45123

不同于数组的旋转做三次旋转的方式,链表无法直接索引,但链表有链表更高效的方式

另外,此题看似简单,但实际有个暗坑,K可能很大,远远大于len,常规方式会导致TLE,对于K很大时,这题是要求按循环链表式处理,比如k=2结果为45123,k=7、k=12、......k=2+len都是结果为45123,也就是分裂点是:idx = k % len

步骤:

1、对于空链表和只有一个元素的链表直接返回

2、遍历一次计算链表长度len,计算分裂点idx = k % len,idx如为0就无需翻转了(如k=0)

3、找到分裂点,如12345,k=2时,找到节点3,然后调整指针即可

OJ61代码:

class Solution {public:    ListNode* rotateRight(ListNode* head, int k) {        if (!head || !head->next) {            return head;        }                //首先计算链表长度len, 这是因为k可能很大, 需要按模计算分裂点        ListNode *st = head, *ed = nullptr;        int len = 1;        while (st->next) {            st = st->next;            ++len;        }        ed = st;                //计算分裂点idx = k % len, 如果发现分裂点为0说明无需翻转, 否则就按常规K翻转        //翻转方式就是找到第(len - idx)个节点, 它后面就是新的前半部分, 调整指针即可        int idx = k % len;        if (!idx) {            return head;        } else {            int st = 1;            ListNode *fast = head;            while (st < (len - idx)) {                fast = fast->next;                ++st;            }            ListNode *newhead = fast->next;            fast->next = nullptr;            ed->next = head;            return newhead;        }    }};

6、OJ143 recorder list

长度为N的链表,要原地调整为:0、N、1、N-1、2、N-2、3......这样的顺序

这题考察的是:链表中点获取、链表部分逆序,双指针操作

步骤:

1、找到链表中点

2、mid后部分逆序

3、双指针调整指针,注意奇数长度的链表,双指针会同时到达mid,偶数长度的链表,左指针到达mid时,右指针在mid右边的元素,注意调整细节

OJ143代码:

class Solution {public:    void reorderList(ListNode* head) {        if (!head || !head->next || !head->next->next) {            return;        }                //找链表中点mid        ListNode *mid = head, *tail = head;        while (tail->next && tail->next->next) {            tail = tail->next->next;            mid = mid->next;        }                //mid后的部分逆序        ListNode *back = mid->next, *prev = mid;        while (back) {            ListNode *next = back->next;            back->next = prev;            prev = back;            back = next;        }                //双指针操作, 重新调整指针, 注意奇数个长度的链表, 双指针会相遇, 偶数个长度的链表, 左指针到达mid时, 右指针会是mid右边的元素, 注意一下调整细节        ListNode *st1 = head, *st2 = prev;        while (1) {            ListNode *st1_next = st1->next, *st2_next = st2->next;            st1->next = st2;            st2->next = st1_next;                        st1 = st1_next;            st2 = st2_next;                        if (st1 == mid) {                if (st1 == st2) {                    st1->next = nullptr;                } else {                    st1->next = st2;                    st2->next = nullptr;                }                return;            }        }    }};

7、OJ86 partition list

如原链表为35891247,给定k,如k=3,原地调整原链表为,小于3的节点在链表左部分,大于等于3的节点做链表右部分

这题需要一个技巧:虽然是原地调整,但通过创建虚拟节点方便处理过程;这是链表题的一个技巧

步骤:

1、创建虚拟节点big、small,分别用于挂接原链表的大值元素和小值元素

2、遍历原链表,大值挂在big下,小值挂在small下

3、删除big和small的虚拟头节点,并连接small和big,返回small的头节点

OJ86代码:

class Solution {public:    ListNode* partition(ListNode* head, int x) {        //链表题的一种技巧, 虽然不让重新创建新链表, 但可以创建一个节点便于处理, 这类就是对大值和小值, 分别创建一个虚拟的头        ListNode *small = new ListNode(INT_MIN), *big = new ListNode(INT_MIN);                //大值往big下放, 小值往small下放        ListNode *st1 = small, *st2 = big;        ListNode *cur = head;        while (cur) {            ListNode *next = cur->next;            int v = cur->val;            if (v >= x) {                big->next = cur;                big = cur;                big->next = nullptr;            } else {                small->next = cur;                small = cur;                small->next = nullptr;            }            cur = next;        }                //删除虚拟节点, 连接大值链表和小值链表        small->next = st2->next;        ListNode *newst = st1->next;        delete st1;        delete st2;        return newst;    }};

8、OJ234 partition linked list

判断链表是否是一个回文

不同于数组的判断回文,链表无法直接索引,链表有链表的方式,本题和OJ143如出一辙,同样考察的是:链表中点、局部逆序、双指针操作

步骤:

1、对于链表节点数为0、1、2的链表自行处理;

2、找中点mid,mid后的部分逆序

3、双指针操作判断是否是回文,同样注意链表长度为奇数、偶数时的细节差别;

4、本题比较恶心的是,判断后还需要还原,即原先逆序的后半部分还要恢复回来

OJ234代码:

class Solution {public:    bool isPalindrome(ListNode* head) {        if (!head) {            return true;        }                //找中点mid        ListNode *mid = head, *fast = head;        while (fast->next && fast->next->next) {            mid = mid->next;            fast = fast->next->next;        }                //提前处理掉只有1-2个节点的情况        if (mid == head) {            if (mid->next) {                return (mid->val == mid->next->val)?true:false;            } else {                return true;            }        }                //mid后的部分逆序        ListNode *cur = mid->next, *prev = mid, *next = cur->next;        if (!next) {            return (head->val == cur->val)?true:false;        } else {            while (next) {                ListNode *nn = next->next;                next->next = cur;                cur->next = prev;                prev = cur;                cur = next;                next = nn;            }        }                //双指针判断回文, 奇数个长度时会相遇在mid,偶数个长度时, 左指针到达mid时, 右指针会在mid后的元素, 注意一下调整        bool res = false;        ListNode *left = head, *right = cur;        while (left != mid) {            if (left->val == right->val) {                left = left->next;                right = right->next;            } else {                res = false;                break;            }        }                if (left == right) {            res = true;        } else {            res = (left->val == right->val)?true:false;        }                //本题比较恶心的是还需要还原原链表        ListNode *p = cur, *q = nullptr, *n = p->next;        while (n != mid) {            ListNode *nn = n->next;            n->next = p;            p->next = q;            q = p;            p = n;            n = nn;        }        return res;    }};

9、OJ19 remove Nth Node From End Of List

删除链表倒数第N个节点

本题看似简单但AC不易,需要考虑多种情况:

1、链表根本不足N个节点

2、整好是倒数第1个节点

所以本题考察的是边界细节处理能力;

步骤:

1、快指针先行N步,如果途中未到N步fast已到头,说明链表不足N个节点无需处理直接返回,如果fast到头且N==0,说明链表整好N个节点

2、如果链表整好N个节点,删除头节点,注意返回第二个节点为新的头

3、如果链表大于N个节点,快慢指针继续同行直到快指针为空,则慢指针到达倒数第N个节点前面的节点,进行链表节点删除

OJ19代码:

class Solution {public:    ListNode* removeNthFromEnd(ListNode* head, int n) {        if (!head || n <= 0) {            return head;        }                //快指针先行N步, 如果链表不足N个节点, fast为nullptr, 如果链表整好N个节点, n==0且fast为nullptr        ListNode *slow = head, *fast = head;        while (n && fast) {            fast = fast->next;            --n;        }                //区分链表大于N个节点和整好N个节点的情况, 前者正常进行, 后者需要删除掉头节点        if (fast) {            while (fast->next) {                slow = slow->next;                fast = fast->next;            }            ListNode *delnode = slow->next;            ListNode *newnode = delnode->next;            slow->next = newnode;            delete delnode;            return head;        } else if (!n) {            ListNode *next = head->next;            delete head;            return next;        }    }};

10、OJ83 remove duplicates from sorted list

有序单链表原地去重,如原先为12334455,原地去重后为12345

考察双指针操作和细心

OJ83代码:

class Solution {public:    ListNode* deleteDuplicates(ListNode* head) {        if (!head) {            return head;        }                ListNode *cur = head, *next = head->next;        int curval = head->val;        while (next) {            if (next->val != curval) {                curval = next->val;                cur = next;                next = cur->next;            } else {                ListNode *nn = next->next;                delete next;                cur->next = nn;                next = nn;            }        }                return head;    }};

11、OJ203 remove linked list elements

删除链表中节点value为给定值的节点

看似简单其实AC十分不易,对于本题没有什么好说的,一定想清楚如下的情况:

1、如果一开头就是要删除的节点,怎么办

2、遇到要删除的节点,怎么办

3、一定处理好往下遍历过程中的next,多思考可能为nullptr的情况

一定要想清楚足够的case

OJ203代码:

class Solution {public:    ListNode* removeElements(ListNode* head, int val) {        if (!head) {            return head;        }                //首先处理掉头节点就为删除点的情况        ListNode *cur = head, *next = cur->next;        while (cur && cur->val == val) {            delete cur;            cur = head = next;            if (cur) {                next = cur->next;            } else {                return head;            }        }                //全是细节处理        while (next) {            if (next->val == val) {                while (next && next->val == val) {                    ListNode *nn = next->next;                    delete next;                    next = nn;                }                cur->next = next;                cur = cur->next;                if (cur) {                    next = cur->next;                } else {                    break;                }            } else {                cur = next;                next = cur->next;            }        }                return head;    }};


12、OJ82 remove duplicates from sorted list II

一点资讯面试原题本人曾踩坑,本题如果按常规做法搞,估计会很被动。。。

如果用循环做,可能相当麻烦,而且极易出错;后来发现这道题适合递归,即把每个找重复的过程用递归连起来,而不是在循环里相互联系;

步骤:

1、当发现节点为nullptr,或者没有next了,则返回该节点

2、取得next;

2.1、如果发现next和当前节点value重复,立即遍历链表直到不重复为止;这样所有值重复节点就会被略过了,然后以这个不重复的节点,递归调用1;

  注意,值重复的节点未做delete释放也是可以AC;

2.2、如果发现next不和当前节点value重复,就用该节点继续递归调用1;

第2步的核心目的是:找到当前节点应该的下一个节点,而递归的方式,巧妙避开了prev、cur这种容易混乱的判断和更新;

OJ82代码:

class Solution {public:    ListNode* deleteDuplicates(ListNode* head) {        //空的或只有它自己, 返回        if (!head) {            return nullptr;        } else if (!head->next) {            return head;        }                //取得next, 如果发现重复, 严查全部的重复节点直到不重复        ListNode *next = head->next;                if (next->val == head->val) {            int dup = head->val;            while (next && dup == next->val) {                next = next->next;            }            return deleteDuplicates(next);        } else {            head->next = deleteDuplicates(next);            return head;        }    }};


阅读全文
0 0
原创粉丝点击