TOP100-链表(四)

9.24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

TOP100-链表(四)_第1张图片

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100] 内
  • 0 <= Node.val <= 100

思路:

使用三个同步指针就可以完成原地交换操作;接着依次向下传递,当其中一快指针出现空时停止交换操作。特别地,对于空链表、单节点链表单独处理即可。

代码:

有些单纯,不给过,重看!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head==nullptr || head->next==nullptr)return head;
        ListNode *pre=new ListNode(0,head);
        ListNode *cur1,*cur2;
        cur1=head;
        cur2=head->next;
        while(cur2!=nullptr){
            cur1->next=cur2->next;
            cur2->next=cur1;
            pre->next=cur2;
            pre=pre->next->next;
            cur2=pre->next->next;
            cur1=cur1->next;
        }
        return head;
    }
};

 改一下:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head==nullptr || head->next==nullptr)return head;
        ListNode *temp=new ListNode(0,head);
        ListNode *pre=temp;
        while(pre->next!=nullptr && pre->next->next !=nullptr){
            ListNode *cur1 =pre->next;
            ListNode *cur2 =pre->next->next;
            pre->next=cur2;
            cur1->next=cur2->next;
            cur2->next=cur1;
            pre=cur1;
        }
        return temp->next;
    }
};

10.25. K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

TOP100-链表(四)_第2张图片

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

TOP100-链表(四)_第3张图片

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
提示:
  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

思路:

仔细

链表分区为已翻转部分+待翻转部分+未翻转部分
每次翻转前,要确定翻转链表的范围,这个必须通过 k 此循环来确定
需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
初始需要两个变量 pre 和 end,pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
经过k此循环,end 到达末尾,记录待翻转链表的后继 next = end.next
翻转链表,然后将三部分链表连接起来,然后重置 pre 和 end 指针,然后进入下一次循环
特殊情况,当翻转部分长度不足 k 时,在定位 end 完成后,end==null,已经到达末尾,说明题目已完成,直接返回即可
时间复杂度为 O(n∗K)最好的情况为 O(n)最差的情况未 O(n^2)
空间复杂度为 O(1) 除了几个必须的节点指针外,我们并没有占用其他空间。

TOP100-链表(四)_第4张图片

该思想源自:

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
    ListNode dummy = new ListNode(0);       // 此处只是设置一个哨兵节点
    dummy.next = head;      // 哨兵节点的下一个指向首节点

    ListNode pre = dummy;       // 上一段的最后一个节点 节点初始化
    ListNode end = dummy;       // 本段最后一个节点

    while (end.next != null) {
        // 此处是为了找到其中的 k 个子节点
        for(int i = 0 ; i < k && end != null; i++){
            end = end.next;
        }

        if(end == null){        // 如果直接到头了,那就说明没有满足 k 个
            break;
        }

        ListNode start = pre.next;           // 此处是为记录原始未反转段的起始节点
        ListNode nextStart = end.next;       // 记录下一个阶段  起始点

        end.next = null;                // 此处是为了进行后面的反转操作,断开此处链接,让后面反转操作知道截断点在哪里
        pre.next = reverse(start);      // 反转操作

        start.next = nextStart;         // 反转之后,start节点实际是已经最后一个节点了,为了和后面的划分段链接,让他的下一个节点连接上下一段的起始点即可
        pre = start;                    // pre再次来到下一段的上一个节点,也就是本段的结尾点
        end = pre;                      // 结束点,准备开始下一段的循环找 k 长度的段操作
        
    }

    return dummy.next;           // 返回最开始的哨兵
}

private ListNode reverse(ListNode head){
    ListNode pre = null;
    ListNode curr = head;

    while(curr != null){        // 交换操作
        ListNode next = curr.next;
        curr.next = pre;
        pre = curr;
        curr = next;
    }

    return pre;     // 返回哨兵,此处是新的翻转序列的起始节点
}
}

11.138. 随机链表的复制

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

示例 1:

TOP100-链表(四)_第5张图片

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

TOP100-链表(四)_第6张图片

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

TOP100-链表(四)_第7张图片

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -10^4 <= Node.val <= 10^4
  • Node.random 为 null 或指向链表中的节点。

思路:

方法:哈希表.

因为本链表与其他链表的不同之处在于多了一个指针,因此一遍不够,可以先遍历一遍正常节点,并建立哈希表,然后再次遍历,补齐指针!

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""
class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head: return
        dic = {}
        # 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        cur = head
        while cur:
            dic[cur] = Node(cur.val)
            cur = cur.next
        cur = head
        # 4. 构建新节点的 next 和 random 指向
        while cur:
            dic[cur].next = dic.get(cur.next)
            dic[cur].random = dic.get(cur.random)
            cur = cur.next
        # 5. 返回新链表的头节点
        return dic[head]

12.148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:

TOP100-链表(四)_第8张图片

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

TOP100-链表(四)_第9张图片

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 10^4] 内
  • -10^5 <= Node.val <= 10^5

进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

思路:

我这直接都准备嘎嘎排序了,你给我来个链表,行!那我直接插入排序咯?划分有序区和无序区,不过代码还得是写一写!!这感觉时间复杂度得是n^2了。算了,换一下,改个归并排序。

对链表自顶向下归并排序的过程如下。

1.找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

2.对两个子链表分别排序。

3.将两个排序后的子链表合并,得到完整的排序后的链表。

上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1个节点时,不需要对链表进行拆分和排序。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head,null);
    }

    public ListNode sortList(ListNode head,ListNode tail){
        if(head==null){
            return head;
        }
        if(head.next==tail){
            head.next = null;
            return head;
        }
        ListNode slow =head,fast = head;
        //快指针走两个,慢指针走一个
        //寻找链表中间位置指针
        while(fast!=tail){
            slow = slow.next;
            fast=fast.next;
            if(fast!=tail){
                fast=fast.next;
            }
        }
        ListNode mid=slow;
        ListNode list1=sortList(head,mid);
        ListNode list2=sortList(mid,tail);
        ListNode sorted=merge(list1,list2);
        return sorted;
    }
    public ListNode  merge(ListNode head1,ListNode head2){
        ListNode dummyHead = new ListNode(0);
        ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
        while (temp1 != null && temp2 != null) {
            if (temp1.val <= temp2.val) {
                temp.next = temp1;
                temp1 = temp1.next;
            } else {
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        if (temp1 != null) {
            temp.next = temp1;
        } else if (temp2 != null) {
            temp.next = temp2;
        }
        return dummyHead.next;
    }
}

13.23. 合并 K 个升序链表

 

思路:

代码:

你可能感兴趣的:(链表,算法,数据结构)