算法基础 典型题(二)链表

记录算法基础题思路:

链表逆序:三指针法,创建一个新链表new_head, 遍历旧链表,1) tmp记录node的next,避免丢失 2)node的next指向new_head  3)更新new_head位置到node 4)node通过tmp更新下一个

反转从位置 m 到 n 的链表:关键记录4个位置,left,right,left前一个,right后一个, 后面两个用于串联逆序后的链表;

链表求交点:计算A,B长度差值,先把长的移动到两边一样长,再一起移动找相同节点地址;

环形链表:快慢指针,fast每次比slow多走一步,只要再fast走到结尾之前fast追上slow就存在环;

链表划分:通过两个新链表一个存大于x,一个小于等于x,再把链表连接起来,利用哑节点简化节点转移

复杂链表复制:两轮遍历,第一次遍历创建节点,并建立map<node, index>, 第二次遍历串联节点,并把random指针通过map填写完成;

链表合并:递归加分治,把链表数量拆分,最小到2个单元的时候进行merge,merge类似通过哑节点简化;

 

step1:

链表逆序:https://leetcode-cn.com/problems/reverse-linked-list/

反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

思路:head往前遍历一遍链表,遍历过程生成head_new指向的逆向链表,节点调整next指向head_new过程会修改指针 必须要先备份节点的next;

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        /* 三指针法 */
        ListNode *new_head = NULL;
        while (head != NULL) {
            ListNode *tmp = head->next; // 先标记next,避免修改next后找不到后继
            head->next = new_head; // 把当前node指向new_head链
            new_head = head; // 更新new_head到新元素;
            head = tmp; // head指向下一个
        }
        return new_head;
    }
};

反转从位置 m 到 n 的链表:https://leetcode-cn.com/problems/reverse-linked-list-ii/

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明: 1 ≤ m ≤ n ≤ 链表长度。

思路:从m、n进行逆序,需要关注的4个位置:1、m的前驱节点(链接 逆序完成后的 头节点) 2、m节点(开始逆序) 3、n节点 4、n的后置节点(逆序后连接尾部节点)。

需要思考特殊场景:如果m==1,从第一个节点开始逆序,应该要返回最后一个反转的节点(逆序后的头结点),否则返回默认的初始头节点;

    ListNode* reverseBetween(ListNode* head, int m, int n) {
        ListNode *cur = head;
        ListNode *reserve_start_pre = NULL;
        int cnt = n - m + 1;//反转次数
        bool start_from_head = m == 1 ? true : false;
        if (n == m && m == 1) {
            return head;
        }
        while (m > 1 && cur != NULL) {
            reserve_start_pre = cur; /* 1 记录反转前pre节点 要链接反转尾节点 */
            cur = cur->next;
            m--;
        }
        ListNode *reserve_start_node = cur; /* 2 记录反转的第一个节点 要连 反转尾节点后一个节点 */
        ListNode *pre = reserve_start_pre;
        while (cnt > 0 && cur != NULL) { /* 3 进行节点反转,反转结束后反转尾节点尾pre,下一个节点尾cur */
                ListNode *next = cur->next;
                cur->next = pre;
                pre = cur;
                cur = next;
                cnt--;
        }
        reserve_start_node->next = cur; /* 连接反转尾后一个节点 */
        if (start_from_head == true) {
            return pre; // 如果从首节点开始,最后返回值应该是,最后一个反转的点
        } else {
            reserve_start_pre->next = pre; /* 连反转后尾部节点 */
            return head;
        }
    }

链表求交点:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

编写一个程序,找到两个单链表相交的起始节点。

思路:假如两个链表有交点,那么A和B链表尾部是链接的,可以从更短的链表位置开始遍历,找到两个相同的位置。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        /* 计算A,B长度差值,先把长的移动到两边一样长,再一起移动 */
        // 计算差值,先移动较长的链表
        int lengthA = 0;
        int lengthB = 0;
        ListNode *tmp = headA;
        while (tmp != NULL) {
            lengthA++;
            tmp = tmp->next;
        }
        tmp = headB;
        while (tmp != NULL) {
            lengthB++;
            tmp = tmp->next;
        }
        while (lengthA != lengthB) {
            if (lengthA > lengthB) {
                lengthA--;
                headA = headA->next;
            } else {
                lengthB--;
                headB = headB->next;
            }
        }
        // 两个相同长度的链表同时往后走,相同为交点
        while (headA != NULL && headB != NULL) {
            if (headA == headB) {
                return headA;
            }
            headA = headA->next;
            headB = headB->next;          
        }
        return NULL;
    }
};

链表求环:https://leetcode-cn.com/problems/linked-list-cycle/

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

思路:利用快慢指针,slow指针进入环后,fast指针每次移动多靠近一格slow,因此如果有环一定会在一圈内追到slow。

如果要求meet的交点也可以按下图公式求,第一次meet后 到焦点距离为c,而从开始的位置到交点具体a=c+n个整圈,可以在meet后搞一个指针从a开始跑,第一次相交为meet;

class Solution {
public:
    bool hasCycle(ListNode *head) {
        /* 快慢指针,fast每次比slow多走一步,只要再fast走到结尾之前fast追上slow就存在环; */
        ListNode *fast = head;
        ListNode *slow = head;
        while (fast != NULL) {
            slow = slow->next;
            fast = fast->next;
            if (fast == NULL) {
                break; /* 走到尾 说明没有环 */
            }
            fast = fast->next; /* fast多走一步 */  
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
};}

链表划分:https://leetcode-cn.com/problems/partition-list/

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5、

思路:设置两个临时哑节点,在遍历链表过程,根据值大小插入对应的哑节点链表中,最后把两个链表链接起来(注意绕过哑巴节点);

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        /* 通过两个新链表一个存大于x,一个小于等于x,再把链表连接起来,利用哑节点简化节点转移 */
        ListNode less(0); // 哑节点简化节点转移
        ListNode more(0);
        ListNode *less_list = &less;
        ListNode *more_list = &more;
        while (head != NULL) {
            if (head->val < x) {
                // 小节点放到less链表
                less_list->next = head;
                less_list = head;
            } else {
                // 大节点放到more链表
                more_list->next = head;;
                more_list = head;     
            }
            head = head->next;
        }
        less_list->next = more.next; // 串联大小链表跳过哑节点
        more_list->next = NULL; // 最后尾部加NULL
        return less.next; // 跳过哑节点返回less起始节点
    }
};

复杂链表复制:https://leetcode-cn.com/problems/copy-list-with-random-pointer/

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。 

思路:做两次遍历,第一次遍历建立一个原链表的拷贝链表,在拷贝的同时将新老链表的节点地址映射到哈希表。
处理拷贝链表的random节点,通过上一步的哈希表找到原链表random节点指向的节点在拷贝链表中对应的节点地址。

    Node* copyRandomList(Node* head) {
        map<Node*, int> pos_map; /* 记录旧链表 的地址 和 pos的对应关系 */
        vector<Node*> node_vector; /* 新链表 ,通过数组 体现 节点位置 和 地址 对应关系 */
        Node * ptr = head;
        int i = 0;
        /* 第一轮编译 创建新节点数组 的 同时生成旧链表 地址-pos关系的map */
        while (ptr != NULL) {
            node_vector.push_back(new Node(ptr->val));
            pos_map[ptr] = i;
            ptr = ptr->next;
            i++;
        }
        node_vector.push_back(NULL); /* 插入一个NULL元素,这样后续链表连接后最后一个指向空 */
        ptr = head;
        i = 0;
        /* 第二轮遍历 连接新链表 的 同时 通过map表 填写新的链表random指针 */
        while (ptr != NULL) {
            node_vector[i]->next = node_vector[i + 1];
            if (ptr->random != NULL) {
                node_vector[i]->random =
                    node_vector[pos_map[ptr->random]];
            }
            ptr = ptr->next;
            i++;
        }
        return node_vector[0];
    }

链表合并:https://leetcode-cn.com/problems/merge-k-sorted-lists/

合并 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度

思路:利用分治+递归,把链表进行递归拆分到2组中,一直到2个链表然后进行合并,最终形成一个完整的链表。

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    ListNode tmp_head(0);
    ListNode *head = &tmp_head;
    while (l1 != NULL && l2 != NULL) {
        if (l1->val > l2->val) {
            head->next = l2;
            head = head->next;
            l2 = l2->next;
        } else {
            head->next = l1;
            head = head->next;
            l1 = l1->next;
        }
    }
    while (l1 != NULL) {
        head->next = l1;
        head = head->next;
        l1 = l1->next;
    }
    while (l2 != NULL) {
        head->next = l2;
        head = head->next;
        l2 = l2->next;
    }
    return tmp_head.next;
}

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.size() == 0) {
            return NULL;
        }
        if (lists.size() == 1) {
            return lists[0];
        }
        if (lists.size() == 2) {
            return mergeTwoLists(lists[0], lists[1]); 
        }

        int mid = lists.size() / 2;
        vector<ListNode *> list_a;
        vector<ListNode *> list_b;
        for (int i = 0; i < mid; i++) {
            list_a.push_back(lists[i]);
        }
        for (int i = mid; i < lists.size(); i++) {
            list_b.push_back(lists[i]);
        }

        ListNode *l1 = mergeKLists(list_a);
        ListNode *l2 = mergeKLists(list_b);
        return mergeTwoLists(l1, l2);
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值