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

这部分考察两个部分:

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;
        }
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值