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、合并两个分区的数据。
链表的基本操作:链表反转,找中间节点,判断是否有环。