mirror of
https://github.com/chefyuan/algorithm-base.git
synced 2024-12-25 11:58:53 +00:00
test
This commit is contained in:
parent
b81ee61e58
commit
dfdec0d511
125
gif-algorithm/链表篇/234. 回文链表.md
Normal file
125
gif-algorithm/链表篇/234. 回文链表.md
Normal file
@ -0,0 +1,125 @@
|
||||
|
||||
|
||||
#### leetcode 234. 回文链表
|
||||
|
||||
请判断一个链表是否为回文链表。
|
||||
|
||||
示例 1:
|
||||
|
||||
```
|
||||
输入: 1->2
|
||||
输出: false
|
||||
```
|
||||
|
||||
|
||||
示例 2:
|
||||
|
||||
```java
|
||||
输入: 1->2->2->1
|
||||
输出: true
|
||||
```
|
||||
|
||||
题目解析:
|
||||
|
||||
题目理解起来很简单,判断是否为回文,如果单纯判断一个字符串或者数组是不是回文很容易。因为数组查询元素的时间复杂度为O(1),但是链表的查询时间复杂度为O(n),而且题目中的链表为单链表,指针只能后移不能前移。所以我们判断起来会比较困难。
|
||||
|
||||
巧用数组法:
|
||||
|
||||
我们首先将链表的所有元素都保存在数组中,然后再利用双指针遍历数组,进而来判断是否为回文。这个方法很容易理解,而且代码实现也比较简单。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public boolean isPalindrome(ListNode head) {
|
||||
//这里需要用动态数组,因为我们不知道链表的长度
|
||||
List<Integer> arr = new ArrayList<Integer>();
|
||||
ListNode copynode = head;
|
||||
//将链表的值复制到数组中
|
||||
while (copynode != null) {
|
||||
arr.add(copynode.val);
|
||||
copynode = copynode.next;
|
||||
}
|
||||
// 双指针遍历数组
|
||||
int back = 0;
|
||||
int pro = arr.size() - 1;
|
||||
while (back < pro) {
|
||||
//判断两个指针的值是否相等
|
||||
if (!arr.get(pro).equals(arr.get(back))) {
|
||||
return false;
|
||||
}
|
||||
//移动指针
|
||||
back++;
|
||||
pro--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这个方法可以直接通过,但是这个方法需要辅助数组,那我们还有其他更好的方法吗?
|
||||
|
||||
双指针翻转链表法
|
||||
|
||||
在上个题目中我们知道了如何找到链表的中间节点,那我们可以在找到中间节点之后,对后半部分进行翻转,翻转之后,重新遍历前半部分和后半部分进行判断是否为回文。
|
||||
|
||||
动图解析:
|
||||
|
||||
![翻转链表部分](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/翻转链表部分.1v2ncl72ligw.gif)
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public boolean isPalindrome(ListNode head) {
|
||||
if (head==null || head.next==null) {
|
||||
return true;
|
||||
}
|
||||
//找到中间节点,也就是翻转的头节点,这个在昨天的题目中讲到
|
||||
//但是今天和昨天有一些不一样的地方就是,如果有两个中间节点返回第一个,昨天的题目是第二个
|
||||
ListNode midenode = searchmidnode(head);
|
||||
//原地翻转链表,需要两个辅助指针。这个也是面试题目,大家可以做一下
|
||||
//这里我们用的是midnode.next需要注意,因为我们找到的是中点,但是我们翻转的是后半部分
|
||||
|
||||
ListNode backhalf = reverse(midenode.next);
|
||||
//遍历两部分链表,判断值是否相等
|
||||
ListNode p1 = head;
|
||||
ListNode p2 = backhalf;
|
||||
while (p2 != null) {
|
||||
if (p1.val != p2.val) {
|
||||
return false;
|
||||
}
|
||||
p1 = p1.next;
|
||||
p2 = p2.next;
|
||||
}
|
||||
// 还原链表并返回结果,这一步是需要注意的,我们不可以破坏初始结构,我们只是判断是否为回文,
|
||||
//当然如果没有这一步也是可以AC,但是面试的时候题目要求可能会有这一条。
|
||||
midenode.next = reverse(backhalf);
|
||||
return true;
|
||||
}
|
||||
//找到中间的部分
|
||||
public ListNode searchmidnode (ListNode head) {
|
||||
ListNode fast = new ListNode(-1);
|
||||
ListNode slow = new ListNode(-1);
|
||||
fast = head;
|
||||
slow = head;
|
||||
//找到中点
|
||||
while (fast.next != null && fast.next.next != null) {
|
||||
fast = fast.next.next;
|
||||
slow = slow.next;
|
||||
}
|
||||
return slow;
|
||||
}
|
||||
//翻转链表
|
||||
public ListNode reverse (ListNode slow) {
|
||||
ListNode low = null;
|
||||
ListNode temp = null;
|
||||
//翻转链表
|
||||
while (slow != null) {
|
||||
temp = slow.next;
|
||||
slow.next = low;
|
||||
low = slow;
|
||||
slow = temp;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
149
gif-algorithm/链表篇/leetcode142环形链表2.md
Normal file
149
gif-algorithm/链表篇/leetcode142环形链表2.md
Normal file
@ -0,0 +1,149 @@
|
||||
#### leetcode 142. 环形链表 II
|
||||
|
||||
题目描述:
|
||||
|
||||
今天给大家介绍比较有特点的题目,也是一个特别经典的题目,判断链表中有没有环,并返回环的入口。
|
||||
|
||||
我们可以先做一下这个题目,就是如何判断链表中是否有环呢?下图则为链表存在环的情况。
|
||||
|
||||
![image-20201027175552961](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027175552961.63sz69rbes00.png)
|
||||
|
||||
判断链表是否有环是很简单的一个问题,我们只需利用之前的快慢指针即可,大家想一下,指针只要进入环内就只能在环中循环,那么我们可以利用快慢指针,虽然慢指针的速度小于快指针但是,总会进入环中,当两个指针都处于环中时,因为移动速度不同,两个指针必会相遇。
|
||||
|
||||
我们可以这样假设,两个孩子在操场顺时针跑步,一个跑的快,一个跑的慢,跑的快的那个孩子总会追上跑的慢的孩子。
|
||||
|
||||
环形链表:
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
public boolean hasCycle(ListNode head) {
|
||||
//特殊情况,无节点或只有一个节点的情况
|
||||
if(head == null || head.next == null){
|
||||
return false;
|
||||
}
|
||||
//设置快慢指针
|
||||
ListNode pro = head.next;
|
||||
ListNode last = head;
|
||||
//循环条件
|
||||
while( pro != null && pro.next!=null){
|
||||
pro=pro.next.next;
|
||||
last=last.next;
|
||||
//两指针相遇
|
||||
if(pro == last){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//循环结束,指针没有相遇,说明没有环。相当于快指针遍历了一遍链表
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
判断链表是不是含有环很简单,但是我们想找到环的入口可能就没有那么容易了。(入口则为下图绿色节点)
|
||||
|
||||
然后我们返回的则为绿色节点的索引,则返回2。
|
||||
|
||||
![image-20201027180921770](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027180921770.21fh8pt3cuv4.png)
|
||||
|
||||
|
||||
|
||||
### HashSet
|
||||
|
||||
我们可以利用HashSet来做,之前的文章说过HashSet是一个不允许有重复元素的集合。所以我们通过HashSet来保存链表节点,对链表进行遍历,如果链表不存在环则每个节点都会被存入环中,但是当链表中存在环时,则会发重复存储链表节点的情况,所以当我们发现HashSet中含有某节点时说明该节点为环的入口,返回即可。
|
||||
|
||||
下图中,存储顺序为 0,1,2,3,4,5,6,**2 **因为2已经存在,则返回。
|
||||
|
||||
![image-20201027182649669](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027182649669.2g8gq4ik6xs0.png)
|
||||
|
||||
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
public ListNode detectCycle(ListNode head) {
|
||||
|
||||
if (head == null) {
|
||||
return head;
|
||||
}
|
||||
if (head.next == null) {
|
||||
return head.next;
|
||||
}
|
||||
//创建新的HashSet,用于保存节点
|
||||
HashSet<ListNode> hash = new HashSet<ListNode>();
|
||||
//遍历链表
|
||||
while (head != null) {
|
||||
//判断哈希表中是否含有某节点,没有则保存,含有则返回该节点
|
||||
if (hash.contains(head)) {
|
||||
return head;
|
||||
}
|
||||
//不含有,则进行保存,并移动指针
|
||||
hash.add(head);
|
||||
head = head.next;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 快慢指针
|
||||
|
||||
这个方法是比较巧妙的方法,但是不容易想到,也不太容易理解,利用快慢指针判断是否有环很容易,但是判断环的入口就没有那么容易,之前说过快慢指针肯定会在环内相遇,见下图。
|
||||
|
||||
![image-20201027184755943](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027184755943.3edot8s2xi60.png)
|
||||
|
||||
上图黄色节点为快慢指针相遇的节点,此时
|
||||
|
||||
快指针走的距离:**a+(b+c)n+b**
|
||||
|
||||
很容易理解b+c为环的长度,a为直线距离,b为绕了n圈之后又走了一段距离才相遇,所以相遇时走的总路程为a+(b+c)n+b,合并同类项得a+(n+1)b+nc。
|
||||
|
||||
慢指针走的距离:**a+(b+c)m+b**,m代表圈数。
|
||||
|
||||
然后我们设快指针得速度是慢指针的2倍,含义为相同时间内,快指针走过的距离是慢指针的2倍。
|
||||
|
||||
**a+(n+1)b+nc=2[a+(m+1)b+mc]**整理得**a+b=(n-2m)(b+c),**那么我们可以从这个等式上面发现什么呢?**b+c**
|
||||
|
||||
为一圈的长度。也就是说a+b等于n-2m个环的长度。为了便于理解我们看一种特殊情况,当n-2m等于1,那么a+b=b+c整理得,a=c此时我们只需重新释放两个指针,一个从head释放,一个从相遇点释放,速度相同,因为a=c所以他俩必会在环入口处相遇,则求得入口节点索引。
|
||||
|
||||
算法流程:
|
||||
|
||||
1.设置快慢指针,快指针速度为慢指针的2倍
|
||||
|
||||
2.找出相遇点
|
||||
|
||||
3.在head处和相遇点同时释放相同速度且速度为1的指针,两指针必会在环入口处相遇
|
||||
|
||||
![环形链表2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/环形链表2.elwu1pw2lw0.gif)
|
||||
|
||||
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
public ListNode detectCycle(ListNode head) {
|
||||
//快慢指针
|
||||
ListNode fast = head;
|
||||
ListNode low = head;
|
||||
//设置循环条件
|
||||
while (fast != null && fast.next != null) {
|
||||
fast = fast.next.next;
|
||||
low = low.next;
|
||||
//相遇
|
||||
if (fast == low) {
|
||||
//设置一个新的指针,从头节点出发,慢指针速度为1,所以可以使用慢指针从相遇点出发
|
||||
ListNode newnode = head;
|
||||
while (newnode != low) {
|
||||
low = low.next;
|
||||
newnode = newnode.next;
|
||||
}
|
||||
//在环入口相遇
|
||||
return low;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
64
gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md
Normal file
64
gif-algorithm/链表篇/leetcode82删除排序链表中的重复元素II.md
Normal file
@ -0,0 +1,64 @@
|
||||
#### 82. 删除排序链表中的重复元素 II
|
||||
|
||||
题目描述
|
||||
|
||||
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。
|
||||
|
||||
示例 1:
|
||||
|
||||
```java
|
||||
输入: 1->2->3->3->4->4->5
|
||||
输出: 1->2->5
|
||||
```
|
||||
|
||||
|
||||
示例 2:
|
||||
|
||||
```java
|
||||
输入: 1->1->1->2->3
|
||||
输出: 2->3
|
||||
```
|
||||
|
||||
> 注意这里会将重复的值全部删除,1,1,2,3最后只会保留2,3。
|
||||
|
||||
这道题目还是很简单的,更多的是考察大家的代码完整性,删除节点也是题库中的一类题目,我们可以可以通过这个题目举一反三。去完成其他删除阶段的题目。
|
||||
|
||||
链表的题目建议大家能有指针实现还是尽量用指针实现,很多链表题目都可以利用辅助空间实现,我们也可以用,学会了那种方法的同时应该再想一下可不可以利用指针来完成。下面我们来思考一下这个题目如何用指针实现吧!
|
||||
|
||||
做题思路:
|
||||
|
||||
这个题目也是利用我们的双指针思想,一个走在前面,一个在后面紧跟,前面的指针就好比是侦察兵,当发现重复节点时,后面指针停止移动,侦察兵继续移动,直到移动完重复节点,然后将该节点赋值给后节点。思路是不是很简单啊,那么我们来看一下动图模拟吧。
|
||||
|
||||
注:这里为了表达更直观,所以仅显示了该链表中存在的节点
|
||||
|
||||
![删除重复节点2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/删除重复节点2.3btmii5cgxa0.gif)
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public ListNode deleteDuplicates(ListNode head) {
|
||||
if(head == null||head.next==null){
|
||||
return head;
|
||||
}
|
||||
ListNode pre = head;
|
||||
ListNode low = new ListNode(0);
|
||||
low.next = pre;
|
||||
ListNode ret = new ListNode(-1);
|
||||
ret = low;
|
||||
while(pre != null && pre.next != null) {
|
||||
if (pre.val == pre.next.val) {
|
||||
while (pre != null && pre.next != null && pre.val == pre.next.val) {
|
||||
pre = pre.next;
|
||||
}
|
||||
pre = pre.next;
|
||||
low.next = pre;
|
||||
}
|
||||
else{
|
||||
pre = pre.next;
|
||||
low = low.next;
|
||||
}
|
||||
}
|
||||
return ret.next;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
151
gif-algorithm/链表篇/关于链表的那些事.md
Normal file
151
gif-algorithm/链表篇/关于链表的那些事.md
Normal file
@ -0,0 +1,151 @@
|
||||
# 链表详解
|
||||
|
||||
阅读完本文你会有以下收获
|
||||
|
||||
1.知道什么是链表?
|
||||
|
||||
2.了解链表的几种类型。
|
||||
|
||||
3.了解链表如何构造。
|
||||
|
||||
4.链表的存储方式
|
||||
|
||||
5.如何遍历链表
|
||||
|
||||
6.了解链表的操作。
|
||||
|
||||
7.知道链表和数组的不同点
|
||||
|
||||
8.掌握链表的经典题目。
|
||||
|
||||
### 链表的定义:
|
||||
|
||||
> 定义:链表是一种递归的数据结构,他或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。
|
||||
|
||||
我们来对其解读一下,链表是一种常见且基础的数据结构,是一种线性表,但是他不是按线性顺序存取数据,而是在每一个节点里存到下一个节点的地址。我们可以这样理解,链表是通过指针串联在一起的线性结构,每一个链表结点由两部分组成,数据域及指针域,链表的最后一个结点指向null。也就是我们所说的空指针。
|
||||
|
||||
### 链表的几种类型
|
||||
|
||||
我们先来看一下链表的可视化表示方法,以便更好的对其理解。
|
||||
|
||||
- 用长方形表示对象
|
||||
- 将实例变量的值写在长方形中;
|
||||
- 用指向被引用对象的箭头表示引用关系。
|
||||
|
||||
#### 单链表
|
||||
|
||||
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
|
||||
|
||||
我们通过上面说到的可视化表示方法,构造单链表的可视化模型,如图所示。
|
||||
|
||||
![image-20201101143220993](E:\Typora笔记\CSDN\leetcode通关笔记\静态图\单链表.png)
|
||||
|
||||
|
||||
|
||||
#### 双向链表
|
||||
|
||||
上面提到了单链表的节点只能指向节点的下一个节点。而双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接,所以双链表既能向前查询也可以向后查询。
|
||||
|
||||
![双链表](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/双链表.3cw4hra1g3q0.png)
|
||||
|
||||
####
|
||||
|
||||
还有一个常用的链表则为循环单链表,则单链表尾部的指针指向头节点。例如在leetcode61旋转链表中,我们就是先将链表闭合成环,找到新的打开位置,并定义新的表头和表尾。
|
||||
|
||||
### 构造链表
|
||||
|
||||
java是面向对象语言,实现链表很容易。我们首先用一个嵌套类来定义节点的抽象数据类型
|
||||
|
||||
```java
|
||||
private class Node {
|
||||
Item item;
|
||||
Node next;
|
||||
}
|
||||
```
|
||||
|
||||
现在我们需要构造一条含有one,two,three的链表,我们首先为每个元素创造一个节点
|
||||
|
||||
```java
|
||||
Node first = new Node();
|
||||
Node second = new Node();
|
||||
Node third = new Node();
|
||||
```
|
||||
|
||||
将每个节点的item域设为所需的值
|
||||
|
||||
```java
|
||||
first.item = "one";
|
||||
second.item = "two";
|
||||
third.item = "three";
|
||||
```
|
||||
|
||||
然后我们设置next域来构造链表
|
||||
|
||||
```java
|
||||
first.next = second;
|
||||
second.next = third;
|
||||
```
|
||||
|
||||
注:此时third的next仍为null,即被初始化的值。
|
||||
|
||||
### 链表的存储方式
|
||||
|
||||
我们知道了如何构造链表,我们再来说一下链表的存储方式。
|
||||
|
||||
我们都知道数组在内存中是连续分布的,但是链表在内存不是连续分配的。链表是通过指针域的指针链接内存中的各个节点。
|
||||
|
||||
所以链表在内存中是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。我们可以根据下图来进行理解。
|
||||
|
||||
![image-20201101153659912](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101153659912.9neaap4ogtc.png)
|
||||
|
||||
### 遍历链表
|
||||
|
||||
链表的遍历我们通常使用while循环(for循环也可以但是代码不够简洁)下面我们来看一下链表的遍历代码
|
||||
|
||||
for:
|
||||
|
||||
```java
|
||||
for (Node x = first; x != null; x = x.next) {
|
||||
//处理x.item
|
||||
}
|
||||
```
|
||||
|
||||
while:
|
||||
|
||||
```
|
||||
Node x = first;
|
||||
while (x!=null) {
|
||||
//处理x.item
|
||||
x = x.next;
|
||||
}
|
||||
```
|
||||
|
||||
### 链表的几种操作
|
||||
|
||||
#### 添加节点
|
||||
|
||||
![image-20201101155937520](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155937520.my13cevp2cg.png)
|
||||
|
||||
|
||||
|
||||
#### 删除节点
|
||||
|
||||
删除B节点,如图所示
|
||||
|
||||
![image-20201101155003257](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155003257.4onlntrwj2i0.png)
|
||||
|
||||
我们只需将A节点的next指针指向C节点即可。
|
||||
|
||||
有的同学可能会有这种疑问,B节点这样不会留着内存里吗?java含有自己的内存回收机制,不用自己手动释放内存了,但是C++,则需要手动释放。
|
||||
|
||||
我们通过上图的删除和插入都是O(1)操作。
|
||||
|
||||
链表和数组的比较
|
||||
|
||||
| | 插入/删除操作(时间复杂度) | 查询(时间复杂度) | 存储方式 |
|
||||
| ---- | ------------------------- | ------------------ | ------------ |
|
||||
| 数组 | O(n) | O(1) | 内存连续分布 |
|
||||
| 链表 | O(1) | O(n) | 内存散乱分布 |
|
||||
|
||||
|
||||
|
45
gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md
Normal file
45
gif-algorithm/链表篇/剑指Offer25合并两个排序的链表.md
Normal file
@ -0,0 +1,45 @@
|
||||
#### 剑指offer25合并两个有序链表
|
||||
|
||||
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
输入:1->2->4, 1->3->4
|
||||
输出:1->1->2->3->4->4
|
||||
```
|
||||
|
||||
今天的题目思路很简单,但是一遍AC也是不容易的。链表大部分题目考察的都是考生代码的完整性和鲁棒性,所以有些题目我们看着思路很简单,但是想直接通过还是需要下一翻工夫的,所以建议大家将所有链表的题目都自己写一下。实在没有时间做的同学,可以自己在脑子里打一遍代码,想清没一行代码的作用。
|
||||
|
||||
迭代法:
|
||||
|
||||
因为我们有两个升序链表,我们需要将其合并,那么我们需要创建一个新节点headpre,然后我们利用双指针思想,每个链表放置一个指针,然后进行遍历并对比当前指针指向的值。然后headpre.next指向较小值的那个节点,不断迭代,直至到达某一有序链表底部,此时一个链表遍历完成,然后我们将未完全遍历的链表接在我们接在合并链表之后即可。
|
||||
|
||||
这是我们迭代做法,另外这个题目还有一个递归方法,目前先不写,等链表掌握差不多的时候会单独写一篇关于递归的文章,也算是为树的题目做铺垫。
|
||||
|
||||
动图讲解:
|
||||
|
||||
![合并数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/合并数组.216f4nn4lti8.gif)
|
||||
|
||||
```java
|
||||
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
|
||||
ListNode headpro = new ListNode(-1);
|
||||
ListNode headtemp = headpro;
|
||||
while (l1 != null && l2 != null) {
|
||||
//接上大的那个
|
||||
if (l1.val >= l2.val) {
|
||||
headpro.next = l2;
|
||||
l2 = l2.next;
|
||||
}
|
||||
else {
|
||||
headpro.next = l1;
|
||||
l1 = l1.next;
|
||||
}
|
||||
headpro = headpro.next;
|
||||
}
|
||||
headpro.next = l1 != null ? l1:l2;
|
||||
return headtemp.next;
|
||||
|
||||
}
|
||||
```
|
||||
|
86
gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md
Normal file
86
gif-algorithm/链表篇/剑指Offer52两个链表的第一个公共节点.md
Normal file
@ -0,0 +1,86 @@
|
||||
#### 剑指 Offer 52. 两个链表的第一个公共节点
|
||||
|
||||
### 前言
|
||||
|
||||
今天给大家带来一个不是那么难的题目,这个题目的解答方法很多,只要能AC的就是好方法,虽然题目不是特别难但是也是剑指offer上的经典题目所以大家要记得打卡呀。
|
||||
|
||||
然后今天我们的链表板块就算结束啦。周末的时候我会对链表的题目做一个总结,俗话说温故而知新嘛。好啦废话不多说,我们一起来看一下今天的题目吧
|
||||
|
||||
题目描述:
|
||||
|
||||
输入两个链表,找出它们的第一个公共节点。如下图,返回黄色结点即可。
|
||||
|
||||
|
||||
|
||||
![image-20201029215837844](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201029215837844.7ezoerpghyk0.png)
|
||||
|
||||
|
||||
|
||||
题目表达是不是也很简单,这个题目我的方法一共有两个,一种就是用HashSet进行存储,一种就是利用双指针,大家有更好的可以在下面讨论呀。
|
||||
|
||||
### HashSet
|
||||
|
||||
这个方法是比较简单的,主要思路就是,先遍历一个链表将链表的所有值都存到哈希表中,然后再遍历另一个链表,如果发现某个结点在哈希表中已经存在那我们直接返回该节点即可,代码也很简单。
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
public ListNode getIntersectionNode (ListNode headA, ListNode headB) {
|
||||
ListNode tempa = headA;
|
||||
ListNode tempb = headB;
|
||||
//定义hash表
|
||||
HashSet<ListNode> arr = new HashSet<ListNode>();
|
||||
while (tempa != null) {
|
||||
arr.add(tempa);
|
||||
tempa = tempa.next;
|
||||
}
|
||||
while (tempb != null) {
|
||||
if (arr.contains(tempb)) {
|
||||
return tempb;
|
||||
}
|
||||
tempb = tempb.next;
|
||||
}
|
||||
return tempb;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面这个方法比较巧妙,不是特别容易想到,大家可以自己实现一下,这个方法也是利用我们的双指针思想。
|
||||
|
||||
下面我们直接看动图吧,特别直观,一下就可以搞懂。
|
||||
|
||||
![第一次相交的点](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/第一次相交的点.5nbxf5t3hgk0.gif)
|
||||
|
||||
是不是一下就懂了呀,我们利用双指针,当某一指针遍历完链表之后,然后掉头去另一个链表的头部,继续遍历。因为速度相同所以他们第二次遍历的时候肯定会相遇,是不是很浪漫啊!
|
||||
|
||||
```java
|
||||
public class Solution {
|
||||
public ListNode getIntersectionNode (ListNode headA, ListNode headB) {
|
||||
//定义两个节点
|
||||
ListNode tempa = headA;
|
||||
ListNode tempb = headB;
|
||||
//循环
|
||||
while (tempa != tempb) {
|
||||
//如果不为空就指针下移,为空就跳到另一链表的头部
|
||||
tempa = tempa!=null ? tempa.next:headB;
|
||||
tempb = tempb!=null ? tempb.next:headA;
|
||||
}
|
||||
return tempa;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
好啦,链表的题目就结束啦,希望大家能有所收获,下周就要更新新的题型啦,继续坚持,肯定会有收获的。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
54
gif-algorithm/链表篇/剑指offer2倒数第k个节点.md
Normal file
54
gif-algorithm/链表篇/剑指offer2倒数第k个节点.md
Normal file
@ -0,0 +1,54 @@
|
||||
# 题目描述:链表中倒数第k个节点
|
||||
|
||||
题目:
|
||||
|
||||
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
|
||||
|
||||
题目分析:
|
||||
|
||||
自己思考一下
|
||||
|
||||
我们遇到这个题目,可能会有什么答题思路呢?
|
||||
|
||||
你看我说的对不对,是不是会想到先遍历一遍链表知道 链表节点的个数,然后再计算出倒数第n个节点。
|
||||
|
||||
比如链表长度为10,倒数第3个节点,不就是正数第8个节点呀,这种方法当然可以啦,是可以实现的,那么我们再思考一下有没有其他方法呢?哦,对,我们可以将链表元素保存到数组里面,然后直接就可以知道倒数第K个节点了。这个方法确实比刚才那个方法省时间了,但是所耗的空间更多了,那我们还有什么方法吗?
|
||||
|
||||
我们可以继续利用我们的双指针呀,但是我们应该怎么做呢?
|
||||
|
||||
双指针法:
|
||||
|
||||
首先一个指针移动K-1位(这里可以根据你的初始化指针决定),然后另一个指针开始启动,他俩移动速度一样,所以他俩始终相差K-1位,当第一个指针到达链表尾部时,第二个指针的指向则为倒数第K个节点。
|
||||
|
||||
![倒数k个节点](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/倒数k个节点.3uh5e9jvbwc0.gif)
|
||||
|
||||
感觉这个方法既巧妙又简单,大家可以自己动手打一下,这个题目是经典题目。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public ListNode getKthFromEnd(ListNode head, int k) {
|
||||
//特殊情况
|
||||
if(head==null){
|
||||
return head;
|
||||
}
|
||||
//初始化两个指针
|
||||
ListNode pro = new ListNode(-1);
|
||||
ListNode after = new ListNode(-1);
|
||||
//定义指针指向
|
||||
pro = head;
|
||||
after = head;
|
||||
//先移动绿指针到指定位置
|
||||
for(int i = 0; i < k-1; i++) {
|
||||
pro=pro.next;
|
||||
}
|
||||
//两个指针同时移动
|
||||
while (pro.next != null) {
|
||||
pro = pro.next;
|
||||
after = after.next;
|
||||
}
|
||||
//返回倒数第k个节点
|
||||
return after;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
60
gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md
Normal file
60
gif-algorithm/链表篇/面试题 02.03. 删除中间节点.md
Normal file
@ -0,0 +1,60 @@
|
||||
#### 面试题 02.03. 删除中间节点
|
||||
|
||||
给定一个头结点为 head的非空单链表,返回链表的中间结点。
|
||||
|
||||
如果有两个中间结点,则返回第二个中间结点。
|
||||
|
||||
**示例 1:**
|
||||
|
||||
```java
|
||||
输入:[1,2,3,4,5]
|
||||
输出:3
|
||||
```
|
||||
|
||||
> 说明:因为只有一个中间节点
|
||||
|
||||
**示例 2:**
|
||||
|
||||
```java
|
||||
输入:[1,2,3,4,5,6]
|
||||
输出:4
|
||||
```
|
||||
|
||||
> 说明:有两个中间节点所以返回后面那个
|
||||
|
||||
## 题目解析:
|
||||
|
||||
又精心筛选了一个题目,本来想写一下删除节点的题目,然后发现这个题目更符合目前的节奏,所以先写一下这个题目,明天再给大家写删除节点的题目。
|
||||
|
||||
大家先不要看我的题解,先自己想一下怎么做。这个这个题目是想让我们找出中间节点,昨天的题目是让我们倒数第K个节点,想一下这两个题目有什么联系呢?
|
||||
|
||||
先说一下刚开始刷题的小伙伴可能会想到的题解,两次遍历链表,第一次遍历获取链表长度,第二次遍历获取中间链表。
|
||||
|
||||
这个方法很OK,利用数组先将所有链表元素存入数组里,然后再直接获得中间节点。这个也很OK,那么我们有没有一次遍历,且不开辟辅助空间的方法呢?
|
||||
|
||||
昨天的题目是一前一后双指针,两个指针之间始终相差k-1位,我们今天也利用一下双指针的做法吧。
|
||||
|
||||
这种类型的双指针是我们做链表的题目经常用到的,叫做快慢指针。
|
||||
|
||||
一个指针走的快,一个指针走的慢,这个题目我们可以让快指针一次走两步,慢指针一次走一步,当快指针到达链表尾部的时候,慢指针不就到达中间节点了吗?
|
||||
|
||||
链表中节点的个数有可能为奇数也有可能为偶数,这是两种情况,但是我们输出是相同的,那就是输出slow指针指向的节点
|
||||
|
||||
![中间节点](E:\Typora笔记\CSDN\leetcode通关笔记\博客动图\中间节点.gif)
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public ListNode middleNode(ListNode head) {
|
||||
ListNode fast = head;//快指针
|
||||
ListNode slow = head;//慢指针
|
||||
//循环条件,思考一下跳出循环的情况
|
||||
while (fast!=null && fast.next != null) {
|
||||
fast = fast.next.next;
|
||||
slow = slow.next;
|
||||
}
|
||||
//返回slow指针指向的节点
|
||||
return slow;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
104
gif-algorithm/链表篇/面试题 02.05. 链表求和.md
Normal file
104
gif-algorithm/链表篇/面试题 02.05. 链表求和.md
Normal file
@ -0,0 +1,104 @@
|
||||
#### 面试题 02.05. 链表求和
|
||||
|
||||
之前我们一起做了链表中的几个经典题型,找到倒数第k个节点,找链表中点,判断链表中环的起点,合并链表,反转链表,删除链表中重复值。这些是链表中的经典问题,面试中也经常会考的问题,然后下面我们继续做一道链表题目,也是面试中经常会考的题目,链表求和问题。
|
||||
|
||||
另外有一些小伙伴说,虽然一天一道题不算多,但是每天读题,做题加消化稍微有点跟不上,所以我打算每个周的工作日进行更新题目,到周末的时候对本周的题目进行总结,然后为大家再写一些别的东西。下面我们一起来看一下今天的题目吧。
|
||||
|
||||
> 为保证严谨性,所有文章中的代码都在网站进行验证,大家可以放心食用。
|
||||
|
||||
题目描述:
|
||||
|
||||
给定两个用链表表示的整数,每个节点包含一个数位。
|
||||
|
||||
这些数位是反向存放的,也就是个位排在链表首部。
|
||||
|
||||
编写函数对这两个整数求和,并用链表形式返回结果。
|
||||
|
||||
示例1:
|
||||
|
||||
```java
|
||||
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
|
||||
输出:2 -> 1 -> 9,即912
|
||||
```
|
||||
|
||||
示例2:
|
||||
|
||||
```java
|
||||
输入:(9 -> 9) + (9 -> 9),即99+99
|
||||
输出:8->9->1
|
||||
```
|
||||
|
||||
示例3:
|
||||
|
||||
```java
|
||||
输入:(5)+(5),即5+5
|
||||
输出:0->1
|
||||
```
|
||||
|
||||
**题目解析:**
|
||||
|
||||
这个题目很容易理解,就是将链表数值进行求和,刚开始做题的同学可能会有这种思路,这个题目我们分别遍历两个链表得到他们的数,然后进行相加,再放到新的链表中,但是这样是行不通的,
|
||||
|
||||
因为我们需要考虑溢出的情况,java 中 int 型的范围为 -2147483648 到 +2147483648,即 -2^31 到 2^31。
|
||||
|
||||
所以链表比较长的话进行求和就会溢出,所以我们不能提取过之后再进行相加,
|
||||
|
||||
我们应该对链表的每一位进行相加,然后通过链表的和,判断是否需要像下一位进行传递,
|
||||
|
||||
就好比小时候我们用竖式进行加法一样,判断两位相加是否大于10,大于10则进1。
|
||||
|
||||
了解了思路,但是想完全实现代码也不是特别容易,这里需要注意的三个点就是,
|
||||
|
||||
1.我们需要根据两个链表的长度,不断对新链表添加节点
|
||||
|
||||
2.需要创建一个变量用来保存进位值。
|
||||
|
||||
3.当跳出循环之后,需要根据进位值来判断需不需要再对链表长度加1.
|
||||
|
||||
这三条可以结合代码理解进行。
|
||||
|
||||
注:进位值只能是0或1,因为每一位最大为9,9+9=18;
|
||||
|
||||
![链表求和](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/链表求和.1yh4ymdee3k0.gif)
|
||||
|
||||
注:这里需要注意得时,链表遍历结束,我们应该跳出循环,但是我们的nlist仍在尾部添加了1节点,那是因为跳出循环时,summod值为1,所以我们需要在尾部再添加一个节点。
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
|
||||
//返回链表
|
||||
ListNode nList = new ListNode(-1);
|
||||
ListNode tempnode = nList;
|
||||
//用来保存进位值,初始化为0
|
||||
int summod = 0;
|
||||
while(l1 != null || l2 != null) {
|
||||
//如果l1的链表为空则l1num为0,若是不为空,则为链表的节点值
|
||||
//判断是否为空,为空就设为0
|
||||
int l1num = l1 == null ? 0 : l1.val;
|
||||
int l2num = l2 == null ? 0 : l2.val;
|
||||
//将链表的值和进位值相加,得到为返回链表的值
|
||||
int sum = l1num+l2num+summod;
|
||||
//更新进位值,例18/10=1,9/10=0
|
||||
summod = sum/10;
|
||||
//新节点保存的值,18%8=8,则添加8
|
||||
sum = sum%10;
|
||||
//添加节点
|
||||
tempnode.next = new ListNode(sum);
|
||||
//移动指针
|
||||
tempnode = tempnode.next;
|
||||
if (l1 != null) {
|
||||
l1 = l1.next;
|
||||
}
|
||||
if (l2 != null) {
|
||||
l2 = l2.next;
|
||||
}
|
||||
}
|
||||
//最后根据进位值判断需不需要继续添加节点
|
||||
if (summod == 1) {
|
||||
tempnode.next = new ListNode(summod);
|
||||
}
|
||||
return nList.next;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user