10 寻找两个链表的第一个公共节点
第一种思路是使用set,把第一个链表全部放入集合之后,在第二个链表遍历的过程中一一判断该节点是否在集合中,这样时间复杂度O(M+N),空间复杂度O(N),不符合要求。
要O(1)的空间复杂度一般就是用双指针,一开始没有想清楚,看了题解恍然大悟,让两个指针分别指向两个链表头,本链表遍历完之后回到另一个链表的头,这样两个指针遍历的长度是一样的,让它们用一样的步长,就可以判断是否有相同节点。时间复杂度O(m+n),空间复杂度O(1).
看到题解还有另一种做法是先找到两个链表各自的长度,让长的那个先走,让两个指针剩余要走的长度一样的时候再同时遍历,本质上还是和上面的思路一样。
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1!=null && p2!=null){
if(p1 == p2) return p1;
p1 = p1.next;
p2 = p2.next;
if(p1==null) p1 = pHead2; //表1遍历完回到表2头
else if(p2 == null) p2 = pHead1;//表2遍历完回到表1头
}
return null;
}
}
需要注意的是!!不能用p1.next=pHead2!!!,因为后面相同的节点会直接形成一个环,永远都不会再出现null!!!
11. 链表相加
一开始没看数据范围,就直接把两个链表遍历算出整数相加再取出各个位的数值,提交后全是错的,因为超出了数据范围,改成了Long也不行,因为位数mn都有10的六次方,显而易见用多长的整形都是不够用的,而且这种做法写起来也并没有简单,必须要一位一位求。
思路很直接,就是先反转一遍链表,从两个尾部开始相加,每次记录最后一位和进位,形成链表最后再反转。时间复杂度O(M和n中较大的那个),空间复杂度O(1)
很想夸一下自己这些都是写完一遍就是正确答案,不需要任何修改。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
ListNode pre = null;
ListNode cur = head1;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
ListNode p1 = pre;//反转后的头
pre = null;
cur = head2;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
ListNode p2 = pre;
ListNode res = new ListNode(-1);
cur = res;
int n = 0;
int m = 0;
while(p1!=null||p2!=null){
if(p1==null){
m = (p2.val+n)%10;
n = (p2.val+n)/10;
}
else if(p2==null){
m = (p1.val+n)%10;
n = (p1.val+n)/10;
}
else {
m = (p1.val+p2.val+n)%10;
n = (p1.val+p2.val+n)/10;//进位
}
ListNode node = new ListNode(m);
cur.next = node;
cur = cur.next;
if(p1!=null) p1 = p1.next;
if(p2!=null) p2 = p2.next;
}
//开始反转
pre = null;
cur = res.next;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
看了一下别人的代码,精简了一下
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
ListNode p1 = reverse(head1);
ListNode p2 = reverse(head2);
ListNode res = new ListNode(-1);
ListNode cur = res;
int jin = 0;
int ge = 0;
while(p1!=null||p2!=null){
int val1 = p1==null? 0:p1.val;
int val2 = p2==null? 0:p2.val;
ge = (val1+val2+jin)%10;
jin = (val1+val2+jin)/10;
cur.next = new ListNode(ge);
cur = cur.next;
if(p1!=null)p1 = p1.next;
if(p2!=null)p2 = p2.next;
}
if(jin!=0) cur.next = new ListNode(jin); //最后一个进位
return reverse(res.next);
}
public ListNode reverse(ListNode head){
ListNode cur = head, pre = null;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
节省了20行的代码量,逻辑也更清晰。
主要是
int val1 = p1==null? 0: p1.val;
int val2 = p2==null? 0: p2.val;
这两行。
11. 链表排序
想法非常直接和简单,把所有的值存到数组中,排序之后再建表,排序时间复杂度刚好是O(Nlogn),空间复杂度O(N)
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
ArrayList<Integer> array = new ArrayList<Integer>();
while(head!=null){
array.add(head.val);
head = head.next;
}
Collections.sort(array);
//list.sort(((a,b)->a-b));也一样
ListNode res = new ListNode(-1);
ListNode cur = res;
for(int i = 0; i< array.size(); i++){
cur.next = new ListNode(array.get(i));
cur = cur.next;
}
return res.next;
}
}
第二种就是归并排序,时间复杂度O(nlogn),空间复杂度O(logn),空间复杂度主要取决于递归调用的栈空间
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
if(head==null|| head.next==null) return head;
ListNode slow = new ListNode(-1);
slow.next = head;
ListNode fast = head;
while(slow!=null && fast!=null && fast.next!=null){
fast = fast.next;
slow = slow.next;
}
//用快慢指针找中点,快指针到结尾时,慢指针刚好到中间
ListNode mid = slow.next;
slow.next = null;//断开,分成两个部分;
return merge(sortInList(head),sortInList(mid)); //归并
}
public ListNode merge(ListNode p1, ListNode p2){
ListNode res = new ListNode(-1);
ListNode cur = res;
while(p1!=null&& p2!=null){
if(p1.val<p2.val){
cur.next = p1;
cur = cur.next;
p1 = p1.next;
}
else{
cur.next = p2;
cur = cur.next;
p2 = p2.next;
}
}
if(p1 == null) cur.next = p2;
else if(p2 == null) cur.next = p1;
return res.next;
}
}
看了题解才知道还有一种自底向上的归并排序,不需要递归,可以节省空间,只需要O(1)的空间复杂度。
使用自底向上的方法实现归并排序,则可以达到 O(1)O(1) 的空间复杂度。
首先求得链表的长度 \textit{length}length,然后将链表拆分成子链表进行合并。
具体做法如下。
用subLength 表示每次需要排序的子链表的长度,初始时subLength=1。
每次将链表拆分成若干个长度为subLength 的子链表(最后一个子链表的长度可以小于 subLength),按照每两个子链表一组进行合并,合并后即可得到若干个长度为subLength×2 的有序子链表(最后一个子链表的长度可以小于subLength×2)。
将subLength 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于length,整个链表排序完毕。
如何保证每次合并之后得到的子链表都是有序的呢?可以通过数学归纳法证明。
初始时subLength=1,每个长度为 11 的子链表都是有序的。
如果每个长度为subLength 的子链表已经有序,合并两个长度为 subLength 的有序子链表,得到长度为subLength×2 的子链表,一定也是有序的。
当最后一个子链表的长度小于subLength 时,该子链表也是有序的,合并两个有序子链表之后得到的子链表一定也是有序的。
因此可以保证最后得到的链表是有序的。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
if(head == null || head.next == null) return head;
ListNode cur = head;
ListNode tmp = new ListNode(-1);
tmp.next = head;
int length = 0;
while(cur!=null){
cur = cur.next;
length++;
}
ListNode readysubhead = tmp;//排好序的子段头
ListNode res = readysubhead;
for(int sublength = 1; sublength < length; sublength =sublength * 2){
cur = tmp.next;
readysubhead = tmp;
while(cur!=null){//遍历所有的子段
ListNode l1 = cur;
for(int i = 1; i< sublength; i++)
{
cur= cur==null?cur:cur.next;//否则数组越界
}
ListNode l2 = cur==null?null:cur.next;
if(cur!=null) cur.next = null;//分割出l1子段
cur = l2;
for(int i = 1; i< sublength; i++)
{
cur= cur==null?cur:cur.next;
}
ListNode subhead = cur==null?null:cur.next;
if(cur!=null) cur.next = null;//分割出l2子段
readysubhead.next = merge(l1,l2);
while(readysubhead.next!=null){
readysubhead = readysubhead.next;
// System.out.print(readysubhead.val);
}
readysubhead.next = subhead;
cur = subhead;
}
}
return res.next;
}
public ListNode merge(ListNode l1, ListNode l2){
ListNode res = new ListNode(-1);
ListNode cur = res;
while(l1!=null && l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
}
else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if(l1==null) cur.next = l2;
else if(l2==null) cur.next = l1;
return res.next;
}
}
debug了非常久,主要是readysubhead忘记更新和cur数组越界的判断问题,需要再练习一下。