算法通关村第一关——链表经典问题之合并有序链表三种方法一层一层优化
题目描述
将两个升序的链表合并为一个新的升序链表并返回,新链表是通过拼接两个给定的两个链表的所有节点组成的。
解题思路
第一种
新建一个链表,然后分别遍历两个链表,每次都按最小的节点接到新链表上,最后排完。
/** 第一个版本
* @param list1
* @param list2
* @return 返回链表头部
*/
public static LinkedNode mergeTwoLists(LinkedNode list1, LinkedNode list2) {
LinkedNode newHead = new LinkedNode(-1);
LinkedNode res = newHead;
while (list1 != null || list2 != null) {
// 情况1 : 都不为空的时候
if (list1 != null && list2 != null) {
if (list1.getData() < list2.getData()) {
newHead.setNext(list1);
list1 = list1.getNext();
} else if (list1.getData() > list2.getData()) {
newHead.setNext(list2);
list2 = list2.getNext();
} else { //相等的情况, 分别接两个链
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
newHead.setNext(list1);
list1 = list1.getNext();
}
newHead = newHead.getNext();
// 情况2 :加入还有链表一个不为空
}else if(list1!=null && list2 == null){
newHead.setNext(list1);
list1 = list1.getNext();
newHead = newHead.getNext();
}else if(list1 == null && list2 !=null){
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
}
}
return res.getNext();
}
第二种
上面那版本的代码,看起来太臃肿了,虽然都能完成,但是所有处理都在一个大While循环里,我们可以将其变得苗条一些。第一个while只处理两个链表都不为空的情况,之后单独写while分别处理list1或者list2不为null的情况
/**
* 改善一点点
* @param list1
* @param list2
* @return 返回头节点
*/
public static LinkedNode mergeTwoListsImprovement(LinkedNode list1,LinkedNode list2){
LinkedNode newHead = new LinkedNode(-1);
LinkedNode res = newHead;
while(list1 != null && list2 != null){
if(list1.getData() < list2.getData()){
newHead.setNext(list1);
list1 = list1.getNext();
} else if (list1.getData() > list2.getData()){
newHead.setNext(list2);
list2 = list2.getNext();
}else{
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
newHead.setNext(list1);
list1 = list1.getNext();
}
newHead = newHead.getNext();
}
// 下面的两个while 最多只有一个会执行
while(list1 != null){
newHead.setNext(list1);
list1 = list1.getNext();
newHead = newHead.getNext();
}
while(list2!=null){
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
}
return res.getNext();
}
第三种
进一步分析,我们发现两个继续优化的点,一个是上面第一个大while里有三种情况,我们可以将其合并成两个,如果两个链表存在相同元素,第一次出现时使用if (l1.val <= l2.val)来处理,后面一次则会被else处理掉,什么意思呢?我们看一个序列。
假如list1为{1, 5, 8, 12},list2为{2, 5, 9, 13},此时都有一个node(5)。当两个链表都到5的位置时,出现了list1.val == list2.val,此时list1中的node(5)会被合并进来。然后list1继续向前走到了node(8),此时list2还是node(5),因此就会执行else中的代码块。这样就可以将第一个while的代码从三种变成两种,精简了很多。
第二个优化是后面两个小的while循环,这两个while最多只有一个会执行,而且由于链表只要将链表头接好,后面的自然就接上了,因此循环都不用写。
/**
* 最终版
* @param list1
* @param list2
* @return
*/
public static LinkedNode mergeTwoListsOptimize(LinkedNode list1,LinkedNode list2){
LinkedNode preHead = new LinkedNode(-1);
LinkedNode prev = preHead;
while(list1 != null && list2 != null){
if(list1.getData() <= list2.getData()){
prev.setNext(list1);
list1 = list1.getNext();
}else{
prev.setNext(list2);
list2 = list2.getNext();
}
prev = prev.getNext();
}
// 最多只有一个还没被合并完,直接上去就行了,这是链表合并方便的地方
prev.setNext(list1 == null ? list2: list1);
return preHead.getNext();
}
整体代码
主代码
package AlgorithmFirst;
/**
* 解决两个有序列表合并成一个有序列表
*/
public class MergeSortedLinedList {
/** 第一个版本
* @param list1
* @param list2
* @return 返回链表头部
*/
public static LinkedNode mergeTwoLists(LinkedNode list1, LinkedNode list2) {
LinkedNode newHead = new LinkedNode(-1);
LinkedNode res = newHead;
while (list1 != null || list2 != null) {
// 情况1 : 都不为空的时候
if (list1 != null && list2 != null) {
if (list1.getData() < list2.getData()) {
newHead.setNext(list1);
list1 = list1.getNext();
} else if (list1.getData() > list2.getData()) {
newHead.setNext(list2);
list2 = list2.getNext();
} else { //相等的情况, 分别接两个链
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
newHead.setNext(list1);
list1 = list1.getNext();
}
newHead = newHead.getNext();
// 情况2 :加入还有链表一个不为空
}else if(list1!=null && list2 == null){
newHead.setNext(list1);
list1 = list1.getNext();
newHead = newHead.getNext();
}else if(list1 == null && list2 !=null){
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
}
}
return res.getNext();
}
/**
* 改善一点点
* @param list1
* @param list2
* @return 返回头节点
*/
public static LinkedNode mergeTwoListsImprovement(LinkedNode list1,LinkedNode list2){
LinkedNode newHead = new LinkedNode(-1);
LinkedNode res = newHead;
while(list1 != null && list2 != null){
if(list1.getData() < list2.getData()){
newHead.setNext(list1);
list1 = list1.getNext();
} else if (list1.getData() > list2.getData()){
newHead.setNext(list2);
list2 = list2.getNext();
}else{
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
newHead.setNext(list1);
list1 = list1.getNext();
}
newHead = newHead.getNext();
}
// 下面的两个while 最多只有一个会执行
while(list1 != null){
newHead.setNext(list1);
list1 = list1.getNext();
newHead = newHead.getNext();
}
while(list2!=null){
newHead.setNext(list2);
list2 = list2.getNext();
newHead = newHead.getNext();
}
return res.getNext();
}
/**
* 最终版
* @param list1
* @param list2
* @return
*/
public static LinkedNode mergeTwoListsOptimize(LinkedNode list1,LinkedNode list2){
LinkedNode preHead = new LinkedNode(-1);
LinkedNode prev = preHead;
while(list1 != null && list2 != null){
if(list1.getData() <= list2.getData()){
prev.setNext(list1);
list1 = list1.getNext();
}else{
prev.setNext(list2);
list2 = list2.getNext();
}
prev = prev.getNext();
}
// 最多只有一个还没被合并完,直接上去就行了,这是链表合并方便的地方
prev.setNext(list1 == null ? list2: list1);
return preHead.getNext();
}
public static void main(String[] args) {
int[] val1 = {1,2,33,43,55,123};
int[] val2 = {10,20,33,34,43,1234};
LinkedNode list1 = LinkedNode.initList(val1);
LinkedNode list2 = LinkedNode.initList(val2);
// 这里要运行哪一个就开哪一个。
// LinkedNode.printLinkedList(mergeTwoLists(list1,list2));
// LinkedNode.printLinkedList(mergeTwoListsImprovement(list1,list2));
LinkedNode.printLinkedList(mergeTwoListsOptimize(list1,list2));
}
}
链表
package AlgorithmFirst;
public class LinkedNode {
private int data;
private LinkedNode next;
public LinkedNode(int data) {
this.data = data;
}
/**
* 获取数据
*
* @return 数据值
*/
public int getData() {
return this.data;
}
/**
* 设置数据的值
*
* @param data 数据
*/
public void setData(int data) {
this.data = data;
}
/**
* 获取下一个节点
*
* @return 当前节点的下一个几点
*/
public LinkedNode getNext() {
return this.next;
}
/**
* 设置下一个节点的值
*
* @param next 下一个节点
*/
public void setNext(LinkedNode next) {
this.next = next;
}
/**
* 获取链表长度
*
* @param head 头节点
* @return
*/
public static int getListLength(LinkedNode head) {
int length = 0;
LinkedNode node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
/**
* 缺省位置,直接在最后插入
*
* @param head 头节点
* @param insertNode 插入节点
* @return 头节点
*/
public static LinkedNode insertNode(LinkedNode head, LinkedNode insertNode) {
int size = getListLength(head);
// return insertNode(head,insertNode,size+1); 修改一下,以便insertNode后面的元素能够全部插入进来。
int count = 1;
LinkedNode temp = head;
if (head == null) {
return insertNode;
}
while (temp != null) {
if (count == size) {
temp.next = insertNode;
temp = null;
} else {
temp = temp.next;
count++;
}
}
return head;
}
/**
* 指定位置插入
*
* @param head 头节点
* @param nodeInsert 插入节点
* @param position 插入位置,从1开始
* @return 返回头节点
*/
public static LinkedNode insertNode(LinkedNode head, LinkedNode nodeInsert, int position) {
if (head == null) {
// 如果head == null 表示当前链表为空,可以直接返回当前节点,或者报异常,这里直接把它当作头节点。
return nodeInsert;
}
// 已经存在的元素的个数
int size = getListLength(head);
if (position > size + 1 || position < 1) {
System.out.println("位置参数越界");
return head;
}
// 表头插入
if (position == 1) {
nodeInsert.next = head;
// 这里可以直接 return nodeInsert
head = nodeInsert;
return head;
}
LinkedNode pNode = head;
int count = 1;
// 这里position 被上面的size限制住了,不用考虑pNode = null
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
/**
* 缺省参数的删除最后一个节点
*
* @param head 链表头节点
* @return 返回新链表头节点
*/
public static LinkedNode deleteNode(LinkedNode head) {
int size = getListLength(head);
return deleteNode(head, size);
}
/**
* 根据位置删除节点
*
* @param head 链表头节点
* @param position 位置从1开始,最大链表大小 超出不删除,返回原头节点。
* @return 新链表头节点
*/
public static LinkedNode deleteNode(LinkedNode head, int position) {
if (head == null) {
// 链表为空,无法删除
return null;
}
int size = getListLength(head);
if (position > size || position < 1) {
System.out.println("输入参数有误");
return head;
}
if (position == 1) {
return head.next;
} else {
LinkedNode cur = head;
int count = 1;
while (count < position - 1) {
cur = cur.next;
count++;
}
LinkedNode curNode = cur.next;
cur.next = curNode.next;
//上面两行可以简化成 : cur.next = cur.next.next
}
return head;
}
public static LinkedNode initList(int[] vals) {
LinkedNode head = null;
for (int val : vals) {
head = insertNode(head, new LinkedNode(val));
}
return head;
}
public static void printLinkedList(LinkedNode head) {
int count = 0;
while (head != null) {
System.out.println("第 " + ++count + " 个:" + head.data);
head = head.next;
}
}
public static void main(String[] args) {
LinkedNode head = new LinkedNode(0);
for (int i = 0; i < 10; i++) {
head = LinkedNode.insertNode(head, new LinkedNode(i + 1));
}
System.out.println("origin:");
printLinkedList(head);
head = deleteNode(head, 3);
System.out.println("delete the third ");
printLinkedList(head);
head = deleteNode(head);
System.out.println("delete the last one");
printLinkedList(head);
head = insertNode(head, new LinkedNode(11));
System.out.println("insert one from last");
printLinkedList(head);
head = insertNode(head, new LinkedNode(22222), 1);
System.out.println("insert to first");
printLinkedList(head);
}
}
近期在自学 Java 做项目,加入了一个编程学习圈子,里面有编程学习路线和原创的项目教程,感觉非常不错。还可以 1 对 1 和大厂嘉宾交流答疑,也希望能对大家有帮助,扫 ⬇️ 二维码即可加入。
也可以点击链接:我正在「编程导航」和朋友们讨论有趣的话题,你⼀起来吧?