今天在LeetCode刷了一道有序链表合并的算法题,一开始是直接把L2链表直接插到了L1里面,后来觉得这样会把原来的L1破坏掉,所以又做了一个把L1、L2放到一个新链表的方法,而且感觉这样更加符合编程的规范(王道)
但这里把两种方法都写一下
方法一:L2插L1
思路:
第一步:先判断L1和L2是否有空的,做对应的处理;
第二步:从L2依次拿一个节点出来和L1每个节点比较大小,如果L2的节点小于L1的某个节点,就把L2节点插入到该L1节点的前面,直到L1或者L2其中有一个遍历完毕;
第三步:检查L2是否还有节点没有插入到L1里面,如果有则直接全部插入到L1后面
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null&&l2==null)
return null;
else if(l1!=null&&l2==null)
return l1;
else if(l2!=null&&l1==null)
return l2;
ListNode la=l1,lb=l2,lc=l1,head=l1,term=null;
while(la!=null&&lb!=null){
if(lb.val<la.val){
term=lb;
lb=lb.next;
term.next=la;
if(la==head){
lc=term;
head=lc;
}
else{
lc.next=term;
term.next=la;
lc=lc.next;
}
}
else{
lc=la;
la=la.next;
}
}
if(la==null&&lb!=null){
lc.next=lb;
}
return head;
}
}
LeetCode的时间复杂度和空间使用如下:
方法二:L1和L2合并到一个新的链表L3
思路:
第一步:先判断L1和L2是否有空的,做对应的处理;
第二步:把L1和L2依次各种拿一个节点出来比较,如果L1的节点小于L2的节点,就构造一个新的中介节点temp,把L1的节点的值放到temp节点里面去,然后把temp节点放到L3链表的后面(如果L3为空就把L3的头节点指向temp),如果L2的节点小于L1的节点就反过来,直到L1或者L2其中有一个遍历完毕;
第三步:检查L1或者L2是否还有节点没有插入到L1里面,如果有则直接全部插入到L3后面(也是要构造中介节点)
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null&&l2==null)
return null;
else if(l1!=null&&l2==null)
return l1;
else if(l2!=null&&l1==null)
return l2;
ListNode la=l1,lb=l2,lc=null,head=null,temp=null;
while(la!=null&&lb!=null){
if(lb.val<la.val){
temp=new ListNode(lb.val);
temp.next=null;
lb=lb.next;
}else{
temp=new ListNode(la.val);
temp.next=null;
la=la.next;
}
if(head==null){
head=temp;
lc=head;
}else{
lc.next=temp;
lc=lc.next;
}
}
if(la==null&&lb!=null){
while(lb!=null){
temp=new ListNode(lb.val);
lc.next=temp;
lc=lc.next;
lb=lb.next;
}
}else if(lb==null&&la!=null){
while(la!=null){
temp=new ListNode(la.val);
lc.next=temp;
lc=lc.next;
la=la.next;
}
}
return head;
}
}
LeetCode的时间复杂度和空间使用如下:
小结
1、我在做第一个方法的时候总是有些粗心大意的地方,比如下面我把term指向lb之后居然忘了把lb指向下一位;以及当要插入的地方是头节点之前的时候我居然把lc.next指向了temp
if(lb.val<la.val){
term=lb;
lb=lb.next;
term.next=la;
if(la==head){
lc=term;
head=lc;
}
2、还有一个最严重的失误就是在程序的最开始忘了判断L1和L2是否有空的,做对应的处理,导致我在判断下面的条件的时候报了空指针异常,这一步里面我找了好久才找出这个错误,不过通过这个错误我也了解到了LeetCode编译器的RunCode是只是将测试数据(TestCode)放到你的程序里面运行,而Commint就会把各种各样的数据放到你的程序上运行,如果有一个测试用例不通过就会报错,我这个就是属于这种情况
if(la==null&&lb!=null){
lc.next=lb;
}
3、两个方法的比较的话,空间上肯定是第二种方法开销更大,因为在L3新链表上面每次都要New一个新的储存空间;时间复杂度上也是第二种方法更大,因为最后还要通过循环来把剩余的L1和L2加入到L3里面去;但第二种方法比较简单,我个人觉得比较好理解,而且也比较符合实际操作(总不能把原来的L1和L2都破坏掉吧)
4、因为我做题都是选择用JAVA语言来做的,这次选到了链表的题目,说实话,这次是我第一次用Java来写链表(之前都是用C语言写的),因为Java里面操纵的标识符实际是指向一个对象的引用(说白了也就是C语言里面的指针),一开始很不习惯,总是下意识的以为是变量,所以还是要加油,看多点Java基础