上两篇内容,对链表和顺序表进行了讲解并手动实现了自己的顺序表和链表,本篇文章将结合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> generate(int num) {List> ans = new ArrayList<>();//零行不做任何处理 直接返回if (num == 0) {return ans;}//走到这儿,说明至少有一行,先添加一行进去ans.add(new ArrayList<>());//对第一行的顺序表添加元素1ans.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 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 步?答案是链表头部headfast=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题~ 感兴趣的老铁店点关注不迷路 谢谢支持!