数据结构与算法-链表练习题

leetcode的几道链表练习题:

package com.freshbin.basics.linkedlist;

/**
 * @author freshbin
 * @date 2020/5/2 14:54
 */
public class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}
package com.freshbin.basics.linkedlist;

import java.util.PriorityQueue;

/**
 * 23. 合并K个排序链表
 *
 * 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
 * 示例:
 * 输入:
 * [
 *   1->4->5,
 *   1->3->4,
 *   2->6
 * ]
 * 输出: 1->1->2->3->4->4->5->6
 *
 * @author freshbin
 * @date 2020/5/3 14:37
 */
public class Solution23 {
    /**
     * 思路:把k个链表的值都放入新数组,对新数组用归并排序或者快速排序,再把新数组的值存入链表
     *
     * @param lists
     * @return
     */
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists == null || lists.length == 0) {
            return null;
        }

        // 获取总的数据量
        int numberSum = 0;
        for(ListNode listNode : lists) {
            while(listNode != null) {
                listNode = listNode.next;
                numberSum++;
            }
        }
        // new一个数组存入所有数据
        int[] newArray = new int[numberSum];
        int index = 0;
        for(ListNode listNode : lists) {
            while(listNode != null) {
                newArray[index++] = listNode.val;
                listNode = listNode.next;
            }
        }

        // 对数组排序
        // 归并排序
//        mergeSort(newArray, 0, numberSum-1);
        // 快速排序
        quickSort(newArray, 0, numberSum-1);

        // 将排序后的数组放入链表
        ListNode newNode = new ListNode(-1);
        ListNode currentNode = newNode;
        for(int val : newArray) {
            currentNode.next = new ListNode(val);
            currentNode = currentNode.next;
        }

        return newNode.next;
    }

    private void quickSort(int[] newArray, int start, int end) {
        if(start >= end) {
            return;
        }

        int targetIndex = partition(newArray, start, end);
        quickSort(newArray, start, targetIndex-1);
        quickSort(newArray, targetIndex+1, end);
    }

    private int partition(int[] newArray, int left, int right) {
        int targetVal = newArray[right];
        while(left < right) {
            while(newArray[left] <= targetVal && left < right) {
                left++;
            }
            newArray[right] = newArray[left];
            newArray[left] = targetVal;
            while(newArray[right] >= targetVal && right > left) {
                right--;
            }
            newArray[left] = newArray[right];
            newArray[right] = targetVal;
        }

        return left;
    }

    private void mergeSort(int[] newArray, int start, int end) {
        if(start >= end) {
            return;
        }

        int middle = (start + end) / 2;
        mergeSort(newArray, start, middle);
        mergeSort(newArray, middle+1, end);
        mergeArray(newArray, start, middle, end);
    }

    /**
     * 合并左右两个数组
     * @param newArray
     * @param start
     * @param middle
     * @param end
     */
    private void mergeArray(int[] newArray, int start, int middle, int end) {
        int left = start, right = middle + 1, k = 0;
        int[] tempArr = new int[end-start+1];
        while(left <= middle && right <= end) {
            if(newArray[left] <= newArray[right]) {
                tempArr[k++] = newArray[left++];
            } else {
                tempArr[k++] = newArray[right++];
            }
        }

        while(left <= middle) {
            tempArr[k++] = newArray[left++];
        }

        while(right <= end) {
            tempArr[k++] = newArray[right++];
        }

        int index = 0;
        for(int i = start; i <= end; i++) {
            newArray[i] = tempArr[index++];
        }
    }

    /**
     * 将k个链表头放入小跟堆中,然后再从中把堆顶元素依次取出来,每取一次,都把当前链表的下一个节点放入堆中
     * 测试发现,放堆中的运行时间还真不如 直接把链表数据都放到数组,再对数组排序后扔回链表,
     * @param lists
     * @return
     */
    public ListNode mergeKListsByHeap(ListNode[] lists) {
        if(lists == null || lists.length == 0) {
            return null;
        }
        // 将k个链表的表头放入优先级队列,即小根堆
        PriorityQueue<ListNode> queue = new PriorityQueue<>((l1, l2) -> l1.val - l2.val);
        for(ListNode listNode : lists) {
            if(listNode != null) {
                queue.add(listNode);
            }
        }

        ListNode head = new ListNode(-1);
        ListNode currentNode = head;
        // 依次取出队列元素,即取出堆顶元素
        while(!queue.isEmpty()) {
            currentNode.next = queue.poll();
            currentNode = currentNode.next;
            if(currentNode.next != null) {
                // 将该链表的下一节点放入队列,即放入堆中
                queue.add(currentNode.next);
            }
        }

        return head.next;
    }

}
package com.freshbin.basics.linkedlist;

import java.util.HashSet;

/**
 * 141. 环形链表
 * 给定一个链表,判断链表中是否有环。
 * 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
 * 如果 pos 是 -1,则在该链表中没有环。
 * 示例 1:
 * 输入:head = [3,2,0,-4], pos = 1
 * 输出:true
 * 解释:链表中有一个环,其尾部连接到第二个节点。
 *
 * @author freshbin
 * @date 2020/5/3 14:37
 */
public class Solution141 {

    /**
     * 思路:遍历链表,用散列表保存遍历过的值
     * 当链表值与散列表值一致,说明出现环,退出
     *
     * @param head
     * @return
     */
    public boolean hasCycle(ListNode head) {
        HashSet hashSet = new HashSet();
        while(head != null) {
            if(hashSet.contains(head)) {
                return true;
            }
            hashSet.add(head);
            head = head.next;
        }

        return false;
    }

    /**
     * 快慢指针法,当快指针与慢指针相等,说明有环
     * @param head
     * @return
     */
    public boolean hasCycle01(ListNode head) {
        if(head == null) {
            return false;
        }

        ListNode slowNode = head;
        ListNode fastNode = head.next;
        if(slowNode == fastNode) {
            return true;
        }
        while(fastNode != null && fastNode.next != null) {
            if(slowNode == fastNode) {
                return true;
            }
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }

        return false;
    }
}
package com.freshbin.basics.linkedlist;

/**
 * 876. 链表的中间结点
 *
 * 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
 * 如果有两个中间结点,则返回第二个中间结点。
 *
 * @author freshbin
 * @date 2020/5/3 14:21
 */
public class Solution876 {
    /**
     * 思路:用两个指针遍历链表,快指针每次都两步,慢指针每次走一步,
     * 如果快指针下一节点或者下下一节点为空,那么就说明快指针走到尽头了
     * 这时候慢指针的下一指针就是中间节点
     *
     * @param head
     * @return
     */
    public ListNode middleNode(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        if(head.next.next == null) {
            return head.next;
        }
        ListNode slowNode = head;
        ListNode fastNode = head.next;

        while(fastNode.next != null && fastNode.next.next != null) {
            fastNode = fastNode.next.next;
            slowNode = slowNode.next;
        }

        // 慢指针的下一节点就是中间节点
        return slowNode.next;
    }

    public static void main(String[] arg) {
        ListNode listNode = new ListNode(1);
        listNode.next = new ListNode(2);
        listNode.next.next = new ListNode(3);
        listNode.next.next.next = new ListNode(4);

        Solution876 solution876 = new Solution876();
        System.out.println(solution876.middleNode(listNode).val);
    }
}
package com.freshbin.basics.linkedlist;

/**
 * 面试题 02.03. 删除中间节点
 *
 * 实现一种算法,删除单向链表中间的某个节点(除了第一个和最后一个节点,不一定是中间节点),假定你只能访问该节点。
 *
 * 示例:
 * 输入:单向链表a->b->c->d->e->f中的节点c
 * 结果:不返回任何数据,但该链表变为a->b->d->e->f
 *
 * @author freshbin
 * @date 2020/5/3 11:16
 */
public class Solution1462 {
    /**
     * 思路:用两个指针遍历链表,快指针每次都两步,慢指针每次走一步,
     * 如果快指针下一节点或者下下一节点为空,那么就说明快指针走到尽头了
     * 这时候慢指针的下一指针中间节点
     *
     * 提交之后发现,神他妈删除中间节点,这踏马入参原来是要删除的节点,
     * 那踏马这不是删除当前节点吗?
     *
     * 将后面节点的值赋到当前节点,然后把当前节点指针指向后节点的下一节点即可
     *
     * @param node
     */
    public void deleteNode(ListNode node) {
        /*if(node == null || node.next == null || node.next.next == null) {
            return;
        }

        ListNode slowNode = node;
        ListNode fastNode = node.next;

        while(fastNode.next != null && fastNode.next.next != null) {
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }

        // 删除中间节点
        slowNode.next = slowNode.next.next;*/

        node.val = node.next.val;
        node.next = node.next.next;
    }

    public static void main(String[] arg) {
        ListNode listNode = new ListNode(1);
        listNode.next = new ListNode(2);
        listNode.next.next = new ListNode(3);
        listNode.next.next.next = new ListNode(4);

        Solution1462 solution1462 = new Solution1462();
        solution1462.deleteNode(listNode.next.next);

        while(listNode != null) {
            System.out.print(listNode.val + "->");
            listNode = listNode.next;
        }
    }
}
package com.freshbin.basics.linkedlist;

/**
 * 面试题18. 删除链表的节点
 * 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
 * 返回删除后的链表的头节点。
 *
 * 示例 1:
 * 输入: head = [4,5,1,9], val = 5
 * 输出: [4,1,9]
 * 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
 *
 * 示例 2:
 * 输入: head = [4,5,1,9], val = 1
 * 输出: [4,5,9]
 * 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
 *
 * @author freshbin
 * @date 2020/5/2 15:43
 */
public class Solution1568 {
    /**
     * 思路:遍历链表,如果当前节点的下一节点值与目标值一致,那么将当前节点的指针指向该节点的下下一节点
     * @param head
     * @param val
     * @return
     */
    public ListNode deleteNode(ListNode head, int val) {
        ListNode currentNode = head;
        if(currentNode == null) {
            return null;
        }
        if(currentNode.val == val) {
            return currentNode.next;
        }
        while(currentNode.next != null) {
            if(currentNode.next.val == val) {
                currentNode.next = currentNode.next.next;
            }
            currentNode = currentNode.next;
        }
        return head;
    }
}
package com.freshbin.basics.linkedlist;

/**
 * 面试题24. 反转链表
 *
 * 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
 *
 * 示例:
 * 输入: 1->2->3->4->5->NULL
 * 输出: 5->4->3->2->1->NULL
 *
 * @author freshbin
 * @date 2020/5/2 14:53
 */
public class Solution1573 {
    /**
     * 思路:遍历链表,用一个辅助链表将当前链表对象放入辅助链表
     * 辅助链表当前对象的next指针指向上一个对象, 同时将链表与辅助链表指针都一直往后移动
     *
     * @param head
     * @return
     */
    public ListNode reverseList(ListNode head) {
        if(head == null) {
            return null;
        }
        ListNode listNode = new ListNode(head.val);
        ListNode currentNode = head.next;
        while(currentNode != null) {
            ListNode next = currentNode.next;
            ListNode preNode = listNode;
            currentNode.next = preNode;
            listNode = currentNode;
            currentNode = next;
        }
        return listNode;
    }

    public static void main(String[] arg) {
        Solution1573 solution1573 = new Solution1573();
        ListNode listNode = new ListNode(1);
        listNode.next = new ListNode(2);
        listNode.next.next = new ListNode(3);
        solution1573.reverseList(listNode);
    }
}

断断续续写了两三天,复习了快排和归并排序,没想到学过又忘记了。

快排:1、快排是先分成左右两个区,分区的节点不用再参与左边或者右边的分区,因为这个节点的左边数据都比该节点小,右边数据都比该节点大,所以这个节点是全局有序的,不用参与左右分区。

2、再对0到分区节点-1继续分区;

3、最后对分区节点+1到最后的索引分区;

归并排序:先分左右部分,再合并。

1、获取中间节点;

2、从左边开始节点到中间节点分左右部分;

3、从中间节点+1到右边最后节点分左右部分;

4、合并两个分区的数据。

链表的基本操作:链表反转,找中间节点,判断是否有环。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值