链表
1.反转链表
核心思想:
1.准备指向当前和前序结点指针。
2.在当前的指针反转之前,保存后续,以便后移
class SolutionBM1 {
public ListNode ReverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
2.反转指定区间链表
具象的描述:一共六本书,要把第2-4反转位置,每次把第二本下面的书放到第一本下面,重复三次就完成了。类似于后插法逆置。
核心思想:
1.因为涉及到第一个结点也可能被操作,所以设置 头结点,next指向head。
2.然后把指定位置前一位的结点作为头,然后指定区间内的结点使用头插法插入。
public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
ListNode headnode = new ListNode(-1);//对于头指针会被操作的结点,引入头结点来使其操作统一。
headnode.next = head;
ListNode pre = headnode;
ListNode cur = head;
int count = 0;
while (cur != null) {
ListNode temp = cur.next;
count++;
//不看if的话就是单纯的遍历链表
//发现在区间内使用头插法。
//在例子1 2 3 4 5【2,4】中。找到2时,固定pre,也就是1,对2-4进行头插。
//头插的关键在于,先让当前结点继承后结点的next,再让后结点插入pre和cur之间。
if (count >= m && count < n) {
cur.next = temp.next;
temp.next = pre.next;
pre.next = temp;
continue;
}
pre = cur;
cur = temp;
}
return headnode.next;
// while(headnode!=null){
// System.out.print(headnode.val+" ");
// headnode = headnode.next;
// }
// // return headnode.next;
// return head;
}
}
3.链表中的结点每k个一组翻转
第2题时指定k个,此题是每k个。
关键思路就是,先理清楚到底有多少段(每段k个)需要反转,每一段的反转逻辑是一样的,所以理所当然的使用循环控制。
public class Solution {
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode reverseKGroup (ListNode head, int k) {
if (head == null) {
return head;
}
//count先作为计数,来统计链表中结点个数
int count = 0;
ListNode cur = head;
while (cur != null) {
cur = cur.next;
count++;
}
//不足k个的段,其结点保持原样。
if (count == 0) {
return head;
}
//定义头结点,方便头指针的反转。
ListNode headnode = new ListNode(-1);
headnode.next = head;
ListNode pre = headnode;
cur = head;
//此时count除k之后,count表示,原链表中前面count段是需要反转的。
count = count / k;
for (int i = 0; i < count; i++) {
//注意是k-1,举例:反转4个结点只需要移动三次。
for (int j = 0; j < k-1; j++) {
//经典的头插法,见第2题。
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
//每次完成一段的反转之后,要移动pre和cur,才能完成后续操作
pre = cur;
cur = cur.next;
}
return headnode.next;
}
}
4.合并两个排序的链表
这个题可以理解成双指针,比较两链表当前cur1、cur2的大小,则其小者放入cur3(cur3最初指向头结点,头结点的next任意指向一个链表即可)
public class Solution {
public ListNode Merge(ListNode list1, ListNode list2) {
if (list2 == null) {
return list1;
}
if (list1 == null) {
return list2;
}
ListNode head = new ListNode(-1);
head.next = list1;
ListNode cur1 = list1;
ListNode cur2 = list2;
ListNode pre = head;
while (cur1 != null && cur2 != null) {
if (cur1.val < cur2.val) {
pre.next = cur1;
cur1 = cur1.next;
} else {
pre.next = cur2;
cur2 = cur2.next;
}
pre = pre.next;
}
if (cur1 != null) {
pre.next = cur1;
} else {
pre.next = cur2;
}
return head.next;
}
}
5.BM5_合并k个已排序的链表
我还没有具体使用ArrayList,目前一个比较简单的想法是。count=ArrayList.size()得到要创建的指针数,每一轮得到最小的数(然后有指针空,count–),当count=1时,直接接上,但是这个做法的时间复杂度是 n*n。
6.BM6_判断链表中是否有环
判断是否有环最好用的就是 双指针中的快慢指针。
一个指针走两步,一个走一步,有环的话就必相遇,无环的话,就快指针必然率先为空。
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode cur1 = head;
ListNode cur2 = head;
while (cur2 != null && cur2.next != null) {
cur1 = cur1.next;
cur2 = cur2.next.next;
if (cur1 == cur2) {
return true;
}
}
return false;
}
}
7.BM7 链表中环的入口结点
此题是一个6的基础上一道数学题。
(上图是来自东哥公众号,侵删)
快指针每次移动两步,慢指针每次移动一步。所以相遇时,快比慢多走了k步(设经历了k次while循环),而此时慢也走了k步,设相遇点距离环起点为m。
慢指针距离起点是k-m;
一圈是k,慢再走k-m可以到环起点;
所以此时再令一个p3从头出发,慢指针继续走,两指针走k-m必然相遇。且相遇点为环起点。
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null) {
return null;
}
ListNode cur1 = pHead;
ListNode cur2 = pHead;
ListNode cur3 = pHead;
while (cur2 != null && cur2.next != null) {
cur1 = cur1.next;
cur2 = cur2.next.next;
if (cur2 == cur1) {
while (cur3 != cur1) {
cur3 = cur3.next;
cur1 = cur1.next;
}
return cur1;
}
}
return null;
}
8.BM8 链表中倒数最后k个结点
此题我想的是弄一个能压缩伸长的杆。
但是因为循环条件是 cur1为空,所以实际做的时候,要注意控制条件的 增1/减1
public class Solution {
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
int count = 0;
ListNode cur1 = pHead;
ListNode cur2 = pHead;
while (cur1 != null) {
count++;//此处懒得再写了,其实可以count=k之后,就停止count的增加,但是不影响
if (count > k) {//为什么是>,因为cur1指向空的时候,cur2相当于多往后移动了1位。所以不能是>=
cur2 = cur2.next;
}
cur1 = cur1.next;
}
if (count >= k) {
return cur2;
} else {
return null;
}
}
}
9.BM9 删除链表的倒数第n个结点
本题相当于就是找倒数第n+1和结点,然后执行删除操作。
因为本体保证n一定有效,所以就不需要像上题最后还要验证,倒数第n+1个到底存不存在。
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
if (head == null) {
return null;
}
ListNode headnode = new ListNode(-1);//建立头结点,因为head也是有可能变动的,这也可以统一处理。
headnode.next = head;
ListNode pre = headnode;
ListNode cur = headnode;
int count = 1;//代表相对于cur1,pre是倒数第几个结点。所以当cur指向空时,我们需要删除的是倒数第n+1个结点(cur为空,相当于整体后移了,所以要删除的是此时倒数n+1)。
while (cur != null) {
if (count > n + 1) {//要删除的是n+1,所以要找倒数第n+2个,所以是>,而非>=。
pre = pre.next;
}
count++;
cur = cur.next;
}
pre.next = pre.next.next;
return headnode.next;
}
}
10.BM10 两个链表的第一个公共结点
这个题老演员了,我一下想到了两种方法。
法一:遍历两链表,记录长度,结合双指针,长的先走|长度差|,然后一起走,结点相等时,则是第一个公共点。
法二:双指针一起走,走完就指向另外一个链表的头指针,然后第一个相遇点就是第一个公共结点。(该方法也最后先算出谁长谁短,方便在代码中加以控制)