【数据结构】链表必刷题 (2)

文章目录

  • 链表分割
  • 环形链表
  • 环形链表Ⅱ
  • 链表指定区间反转
  • 链表中的节点每k个一组反转
  • 链表的奇偶重排
  • 链表相加Ⅱ
  • 重排链表

链表分割

OJ地址

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

    public ListNode partition(ListNode pHead, int x) {
        // write code here
        //存放小于x的区域
        ListNode aHead = null;
        ListNode aEnd = null;
        //存放大于x的区域
        ListNode bHead = null;
        ListNode bEnd = null;
        //用cur遍历,分开数据
        ListNode cur = pHead;
        
        while (cur != null) {
            if (cur.val < x) {
                //判断是否存在数据
                if (aHead == null) {
                    aHead = cur;
                    aEnd = cur;
                } else {
                    aEnd.next = cur;
                    aEnd = aEnd.next;
                }
            } else {
                if (bHead == null) {
                    bHead = cur;
                    bEnd = cur;
                } else {
                    bEnd.next = cur;
                    bEnd = bEnd.next;
                }
            }
            cur = cur.next;
        }
        //情况讨论        
        //全部都是小于x的数据
        if (aHead == null) {
            return bHead;
        }
        aEnd.next = bHead;
        //全部都是大于x的数据
        if (bHead != null) {
             //两种都有  
            bEnd.next = null;//注意。尾节点的结点域要置空    
        }                 
        return aHead;       
    }

环形链表

OJ地址

给你一个链表的头节点 head ,判断链表中是否有环。

思路:快慢指针,链表有环就是把尾节点的指针域指向任意一个结点,这样就创建一个带环链表,解决这种问题的思路还是快慢指针,快指针的速度一直是慢指针的2倍,打个比方,你和你朋友在操场跑步,只要一直跑下去,你们始终会相遇。

    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
       return false;
    }
问题延申:如何找到环的长度呢?

答案:快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度

环形链表Ⅱ

OJ地址

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

思路1 :快慢指针

【数据结构】链表必刷题 (2)_第1张图片

    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                fast = head;
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }

思路2:利用哈希表不包含重复元素的特性。

    public ListNode detectCycle(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();        
        while (head != null) {
            if (set.contains(head)) {
                return head;
            }
            set.add(head);
            head = head.next;
        }
        return null;       
    }

链表指定区间反转

OJ链接
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。

与之前的反转链表的思路是差不多的,只不过这里我们,需要再区间内反转,那么我们必须要找到第m个结点的前一个结点pre,接下来的思路如下图:

【数据结构】链表必刷题 (2)_第2张图片

    public ListNode reverseBetween (ListNode head, int m, int n) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;       
        ListNode pre = dummyHead;
        n = n - m;
        //找到m位置的前一个结点
        while (m != 1) {
            pre = pre.next;
            m--;
        }
        ListNode cur = pre.next;        
        while (n != 0){
            ListNode curNext = cur.next;
            cur.next = curNext.next;
            curNext.next = pre.next;
            pre.next = curNext;
            n--;
        }
        return dummyHead.next;           
    }

链表中的节点每k个一组反转

OJ链接

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。

思路:先获取链表长度,然后分为 size/k组,每一组依次反转,是上一题的思路的进阶
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode pre = dummyHead;
        ListNode cur = head;
        
        //获取长度
        int size = 0;
        while (cur != null) {
            cur = cur.next;
            size++;
        }
        //分为size / k 组 
        cur = head;
        for (int i = 0; i < size / k; i++) {
            for (int j = 0; j < k - 1; j++) {
                ListNode curNext = cur.next;
                cur.next = curNext.next;
                curNext.next = pre.next;
                pre.next = curNext;
            }
            pre = cur;
            cur = cur.next;                      
        }
        return dummyHead.next;
    }

链表的奇偶重排

OJ链接

给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。
注意是节点的编号而非节点的数值。

思路:定义两个指针一个找奇结点,另一个找偶结点,然后分别链接,最后把奇结点的尾链接到偶结点的头。

    public ListNode oddEvenList (ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }    
        //定义一个指向偶数结点的头指针
        ListNode evenHead = head.next;
        //一直指向偶结点
        ListNode even = evenHead;
        //一直指向奇结点
        ListNode odd = head;       
        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }

链表相加Ⅱ

OJ链接

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。

思路:由于加法是从最后开始计算的,所以我们先逆置链表,然后计算每一个结点的值,注意进位,然后逆置头节点。

    public ListNode addInList (ListNode head1, ListNode head2) {
        // write code here
        if (head1 == null) {
            return head2;
        } 
        if (head2 == null) {
            return head1;
        }
        //反转链表
        head1 = reverse(head1);
        head2 = reverse(head2);
        ListNode head = new ListNode(0);
        ListNode cur = head;
        int carry = 0;
        
        while (head2 != null || head1 != null) {
            int sum = carry;
            if (head1 != null) {
                sum += head1.val;
                head1 = head1.next;
            }
            if (head2 != null) {
                sum += head2.val;
                head2 = head2.next;
            }
            carry = sum / 10;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;
        }
        if (carry != 0) {
            cur.next = new ListNode(carry);
        }
        return reverse(head.next);
        
    }
    public ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return pre;
    } 

思路:为了把数据逆置,我们可以利用栈的特性,把数据放入栈中,拿出来时就是从末尾开始,然后依次相加,注意进位。

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();

        while (l1 != null) {
            stack1.push(l1.val);
            l1 = l1.next;
        }

        while (l2 != null) {
            stack2.push(l2.val);
            l2 = l2.next;
        }
        int carry = 0;
        ListNode head = null;
        while (!stack2.empty() || !stack1.empty() || carry > 0) {
            int sum = carry;
            sum += stack1.empty() ? 0 : stack1.pop();
            sum += stack2.empty() ? 0 : stack2.pop();
            ListNode node = new ListNode(sum % 10);
            carry = sum / 10;
            //头插
            node.next = head;
            head = node;
        }
        return head;
    }

重排链表

OJ链接

【数据结构】链表必刷题 (2)_第3张图片
思路:先找到中间结点,逆置后部分,插入前部分结点间隙

class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }
        //找到中间结点
        ListNode mid = findMid(head);

        ListNode front = head;
        ListNode back = mid.next;
        mid.next = null;
        //反转后面的结点
        back = reverse(back);
        //依次插入前半部分间隙
        merge(front, back);
    }
    public ListNode findMid(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    public ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return pre;
    }
    public void merge(ListNode front, ListNode back) {
        while (front != null && back != null) {
            ListNode frontNext = front.next;
            ListNode backNext = back.next;

            front.next = back;
            front = frontNext;

            back.next = front;
            back = backNext;
        }
    }

}

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