文章目录
- 前言
- 一、合并两个有序链表
- 二、使用顺序表实现“杨辉三角”
- 三、环形链表
- 四、环形链表Ⅱ
- 总结
前言
上两篇内容,对链表和顺序表进行了讲解并手动实现了自己的顺序表和链表,本篇文章将结合LeetCode上的OJ题,进行具体的使用以熟悉其中的方法和使用细节。
一、合并两个有序链表
题目链接:Leetcode.21题
有序、链表这是题目中已经说明的,具体思路是我们创建一个新的头结点用来保存合并后的链表。分别遍历两个链表的头节点,将较小的头结点链接在新的链表后 然后头节点后移继续比较,直到一个链表为空(当有一个链表为空时,只需要将另一个链表剩余部分全部连接在答案链表之后)。
代码实现:
public class Leetcode21_MergeTwoList {
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head1=list1;
ListNode head2=list2;
//定义一个储存答案的头结点
ListNode ansHead= new ListNode();
ListNode ansLast=ansHead; //尾节点
while (head1!=null&&head2!=null) {
//比较两个链表头结点的大小
if (head1.val < head2.val) {
//先将待连接的头节点保存下来
ListNode cur = head1;
//链表1头节点后移
head1 = head1.next;
//将之前保存的头节点连接到结果链表中
ansLast.next = cur;
//更新结果链表的尾节点
ansLast = cur;
} else {
ListNode cur = head2;
head2 = head2.next;
ansLast.next = cur;
ansLast = cur;
}
}
if(head1!=null){
ansLast.next=head1;
}else {
ansLast.next=head2;
}
return ansHead.next;
}
}
测试代码正确性:
//测试代码正确性
public static void main(String[] args) {
ListNode listN1 = new ListNode(100);
ListNode listN2 = new ListNode(200);
ListNode listN3 = new ListNode(300);
listN1.next = listN2;
listN2.next = listN3;
listN3.next=null;
ListNode listN4 = new ListNode(100);
ListNode listN5 = new ListNode(200);
ListNode listN6 = new ListNode(300);
listN4.next=listN5;
listN5.next=listN6;
listN6.next=null;
ListNode ans=mergeTwoLists(listN1,listN4);
ListNode cur=ans;
while (cur!=null){
System.out.print(cur.val+" ");
cur=cur.next;
}
}
运行结果:
二、使用顺序表实现“杨辉三角”
题目链接:Leetcode.118题
这道题,我们需要使用List接口下的ArrayList。我们可以创建一个线性表,线性表中的每一个元素也是一个线性表,每个线性表是一个ArrayList,存储的是该行对应的元素。需要特殊处理的是三角形的前两行,第三行开始只需要首尾填1,其他使用循环结构从上一行取元素加和即可。
代码实现:
class Solution {
public static List<List<Integer>> generate(int num) {
List<List<Integer>> ans = new ArrayList<>();
//零行不做任何处理 直接返回
if (num == 0) {
return ans;
}
//走到这儿,说明至少有一行,先添加一行进去
ans.add(new ArrayList<>());
//对第一行的顺序表添加元素1
ans.get(0).add(1);
//判断是不是只有一行,若是 返回
if (num == 1) {
return ans;
}
//走到这儿说明至少有两行 再添加一行(一个顺序表)进去
ans.add(new ArrayList<>());
//处理第二行元素
ans.get(1).add(1);
ans.get(1).add(1);
//判断是否只有两行
if (num == 2) {
return ans;
}
//走到这儿 就有规律可循了 第三行之后每一行只有首尾是1,其他元素根据上一行得到
for (int i = 3; i <= num; i++) {
//每走一趟循环 添加一行进去
List<Integer> cur = new ArrayList<>();
ans.add(cur);
ans.get(i - 1).add(1); //这一行的首元素
//循环体中对每一行除首尾之外的元素进行处理
for (int j = 0; j < i - 2; j++) {
int first = ans.get(i - 2).get(j); //获取上一行第j个元素
int second = ans.get(i - 2).get(j + 1);//上一行第j+1个元素
int e = first + second; //求和
ans.get(i - 1).add(e); //添加到这一行的顺序表中
}
ans.get(i - 1).add(1); //这一行的尾元素
}
return ans;
}
}
测试代码正确性:
public static void main(String[] args) throws InterruptedException {
System.out.println(Leetcode118_Generate.generate(6));
}
运行结果:
三、环形链表
题目链接:Leetcode.141题
思路一:哈希表,这道题比较容易想到的一个思路是,我们可以遍历这条链表,每次遍历时判断该节点是否被访问过。可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。但是哈希表方法还没总结,因此我们使用一个更简单的方法。
思路二:快慢指针,此方法为Floyd判圈算法(又称龟兔赛跑算法),假想乌龟和兔子同时从同一起点在链表上移动,如果链表上没有环,那么兔子将一直处于乌龟的前方;若该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。
我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,块慢指针都在位置 head,这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
代码实现:
public boolean hasCycle(ListNode head){
//先处理空链表和只有一个结点的情况
if(head==null){
return false;
}
if(head.next==null){
return false;
}
//定义快慢指针
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){ //fast.next!=null 这一句很关键 没有的话会报空指针异常~
fast=fast.next.next; //没有fast.next!=null这个条件的话 这里会空指针异常
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
测试代码正确性:
public static void main(String[] args) {
ListNode listN1 = new ListNode(100);
ListNode listN2 = new ListNode(200);
ListNode listN3 = new ListNode(300);
listN1.next = listN2;
listN2.next = listN3;
listN3.next = listN1;
System.out.println(hasCycle(listN1));
}
四、环形链表Ⅱ
题目链接:Leetcode.142题
与上一题不同的是,上一题只需要判断链表中有没有环,此题需要找到并返回环的入口节点,难点在于如何找到?
判断是否有环上面已经讲过了,此时找环的入口:
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2: (x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
这样我们只需要对上面的代码稍作修改,就可以AC这道题了!
代码如下:
public ListNode detectCycle(ListNode head){
if(head==null){
return null;
}
if(head.next==null) {
return null;
}
ListNode fast=head;
ListNode slow=head;
while (fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
break;
}
if(fast==null||fast.next==null){
return null;
}
}
//运行到此处 说明存在环,快指针比慢指针多走了 n倍的环的长度
//如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=x+n(y+z)(先走 x 步到入口节点,之后每绕 1 圈环( y+z 步)都会再次到入口节点)。
//而目前,slow 指针走过的步数为 n(y+z) 步。因此,我们只要想办法让 slow 再走 x 步停下来,就可以到环的入口。
//但是我们不知道 x 的值,该怎么办?依然是使用双指针法。我们构建一个指针,此指针需要有以下性质:此指针和slow 一起向前走 x 步后,两者在入口节点重合。那么从哪里走到入口节点需要 x 步?答案是链表头部head
fast=head;
while (fast!=slow){
slow=slow.next;
fast=fast.next;
}
return slow;
}
验证代码正确性:
public static void main(String[] args) {
//listN1是头节点也是链表环的入口
ListNode listN1 = new ListNode(100);
ListNode listN2 = new ListNode(200);
ListNode listN3 = new ListNode(300);
listN1.next = listN2;
listN2.next = listN3;
listN3.next = listN1;
System.out.println(detectCycle(listN1).val);//输出入口结点的值
}
总结
本篇博客通过四道力扣题对顺序表和链表的使用以及其具体实现了ArrayList、LinkedList进行了使用,后面还会更新更多的Leetcode题~ 感兴趣的老铁店点关注不迷路 谢谢支持!