1.从尾到头打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList
我的解法
这道题挺简单的,链表不像数组能通过下标取元素,链表只能从头开始一个一个遍历到要取的元素,通过数组方法unshift从头往数组添加元素,这样就是从尾到头顺序了。
function printListFromTailToHead(head)
{
var array=[]
var current=head
while(current){
array.unshift(current.val)
current = current.next
}
return array
}
2.链表中环的入口结点
我的解法
当然是错的,从头到尾遍历结点,给结点添加一个属性,如果有环的话,那么会有某个结点指向之前被遍历过的结点,那么这个结点就有被添加的属性。不过这样做,破坏了原有的数据
其他分析
1.设置两个指针,一快一慢,快指针的速度是慢指针速度的二倍。如果有环,那么二者一定会在环中某结点相遇。
2.然后一个指针从头结点出发,另一个在相遇结点出发,但是速度一致,二者再次相遇的结点就是环入口
证明
1.指针fast每次走两步,慢指针slow每次都一步,当慢指针进入环后,就是追逐物理题了,二者肯定会相遇。
2.如上图,假设AC长度为a,CB长度为b,BC长度为c。相遇时:
-
fast指针走过的路程为a+k(b+c)+b,k>=1
-
slow指针走过的路程为a+b。
关于公式有两点说明:
- k>=1是一定的,如果fast一环都没绕就被slow追上了,二者相同时间经过的路程一样,这与二者速度不同相悖
- slow肯定没绕环,如果slow绕了一环,fast肯定绕了两环,早就相遇了
故有2(a+b)=a+k(b+c)+b,k>=1,从而得到a=(k-1)(b+c)+c,k>=1。a的表达式意义为:链表头到环入口的距离=相遇点到环入口的距离+(k-1)圈环长度。两个指针速度相同,在相同的时间内经过相同长度的路程,从A点出发的指针一定会和从C点出发绕环的指针在环入口处相遇。
function EntryNodeOfLoop(pHead){
var fast = pHead
var slow = pHead
while(fast!=null&&fast.next!=null){
slow = slow.next
fast = fast.next.next
if(slow == fast){
let p = pHead
while(p!=slow){
p = p.next
slow = slow.next
}
return p
}
}
return null
}
一点补充
环的长度:B点在环内,新建变量记录该点位置,指针从该点出发,一边走一边计数一边判断当前点与记录,当回到记录点时,计数即为环长。
链表的长度:环长已知,再加上a的值即为链表长度了。
3.删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
我的解法
错到离谱,不过也加深了对链表结构的理解,比如链表是如何删除的(谁能想到我看了一天这道题呢)
其他分析
1.怎样删除链表的某个结点?是通过改变前一个结点的next指针指向。
- 所以本题中要删除结点,肯定要一个中间变量记录当前结点cur的前结点,这里设为pre。
- 另外,考虑一个特殊情况那就是第一个结点与第二个结点重复,那么第一个结点怎么删?它没有前结点啊!所以要设置一个头结点,帮助我们删第一个结点
2.pre指针的作用:它是当前结点的前结点,改变它的指针来跳过重复的结点指向下一个不重复的点。那是前面的哪个结点呢?就是当前确定的不重复的点,这样当我们遍历到下一个不重复的点的时候,把pre指针指向这个点就行了。
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function deleteDuplication(pHead)
{
var Head = new ListNode(0) //设置一个头结点
Head.next = pHead //指向第一个结点
var pre = Head
var cur = pHead
while(cur != null){
if(cur.next!=null&&cur.val == cur.next.val){
while(cur.next!=null&&cur.val == cur.next.val){
cur = cur.next
}
pre.next = cur.next
cur = cur.next
}else{
pre = pre.next
cur = cur.next
}
}
return Head.next
}
3.越界问题的处理
自己一开始写的循环条件如下,直接在最外层循环判断下一结点是否为空,是的说明当前结点是最后一个结点故跳出循环。但是这样做,当链表为空的时候就会报错,所以这样做是不对的。
while(cur.next != null){
if(cur.val == cur.next.val){
while(cur.val == cur.next.val){
4.pre的更新:这个我想了很久,可能太蠢。
当发现接下来有重复结点的时候,我们找到重复最后一个结点然后要做的是删!所以改变pre的next指针
pre.next = cur.next
当发现下一个结点和当前结点不同时,pre是从前一个位置(这个位置上的结点是确定的不重复的点)更新到最新的不重复的点
pre = pre.next
4.复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
我的解法
我哪有解法
其他分析
三步法
剑指给出的方法很直观,画个图,思路清晰,代码其实很好写
function Clone(pHead)
{
if(pHead == null) return null
let pNode = pHead
//第一步:复制每个结点,把新结点插入旧结点的后面
while(pNode){
let cloneNode = new RandomListNode(pNode.label)
cloneNode.next = pNode.next //注意赋值顺序
pNode.next = cloneNode
pNode = cloneNode.next
}
//第二步:旧结点的random复制
pNode = pHead
while(pNode){
if(pNode.random){ //这个判断要有
pNode.next.random = pNode.random.next
}
pNode = pNode.next.next
}
//第三步:拆分链表
pNode = pHead
let cloneHead = pHead.next
let cloneNode = cloneHead
while(pNode){
pNode.next = pNode.next.next
if(cloneNode.next){
cloneNode.next = cloneNode.next.next
}
pNode = pNode.next
cloneNode = cloneNode.next
}
return cloneHead
}
哈希法
用一个哈希表表示新旧结点之间的映射关系:键是旧结点,值就是新结点。这里就要用到es6中新的数据结构map:特殊的对象,它的键可以是对象。
- 第一次遍历:复制旧结点的值和next指针,用map保存新旧结点之间的映射关系
- 第二次遍历:通过map获得旧节点对应的新节点,更新 新结点的random 指针
function Clone(pHead)
{
if(pHead == null) return null
let map = new Map()
let pNode = pHead
//新链表的头
let cloneHead = new RandomListNode(pHead.label)
let cloneNode = cloneHead
map.set(pNode,cloneNode) //先把两个头放进去,代码会简洁一点
//第一次遍历:复制旧结点的值和next
while(pNode){
//创建新结点
if(pNode.next){ //这个判断一定要有
cloneNode.next = new RandomListNode(pNode.next.label)
}else{
cloneNode.next = null
}
cloneNode = cloneNode.next
pNode = pNode.next
map.set(pNode,cloneNode) //结点对存入map中
}
//第二次遍历:复制random指针
pNode = pHead
while(pNode){
if(pNode.random){
//map的get方法:由键取值
map.get(pNode).random = map.get(pNode.random)
}
pNode = pNode.next
}
return cloneHead
}
5.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
我的解法
结果要排序说明要用中序遍历,这样才是从小到大。遍历的同时创建结点,以及改变指针指向。所以我真的不会
其他分析
剑指有如下分析,也就是说需要记录左子树的最大结点和右子树的最小结点。这样和根结点链接,整棵树就转换好了。
这里用一个指针pre记录。
递归
- 明确Convert函数的功能。
输入:输入一个二叉搜索树的根节点。
过程:将其转化为一个有序的双向链表。
输出:返回该链表的头节点。 - 明确成员变量last的功能。
last用于记录当前链表的末尾节点。 - 明确递归过程。
递归的过程就相当于按照中序遍历,将整个树分解成了无数的小树,然后将他们分别转化成了一小段一小段的双向链表。再利用past记录总的链表的末尾,然后将这些小段链表一个接一个地加到末尾。
递归的代码真的头疼,一开始我的代码是这样的
function Convert(pRootOfTree)
{
if(pRootOfTree == null) return null
let last = null
ConvertCore(pRootOfTree,last)
let pHead = pRootOfTree
while(pHead.left){
pHead = pHead.left
}
return pHead
}
function ConvertCore(root,last){
if(root == null) return
ConvertCore(root.left,last)
root.left = last
if(last) last.right = root
last = root
ConvertCore(root.right,last)
}
报错如下
树的结构如下,可知4,6,8,12四个结点都没了。
一直没找到出错原因,改成如下代码后正常了, 我猜测是last更新有问题。
function Convert(pRootOfTree)
{
if(pRootOfTree == null) return null
let last = null
ConvertCore(pRootOfTree,last)
let pHead = pRootOfTree
while(pHead.left){
pHead = pHead.left
}
return pHead
}
function ConvertCore(root,last){
if(root == null) return
if(root.left){
last = ConvertCore(root.left,last)
}
root.left = last
if(last) last.right = root
last = root
if(root.right){
last = ConvertCore(root.right,last)
}
return last
}
非递归
中序递归的同时,需要一个指针pre记录当前链表的末尾结点。真的比递归好理解很多。
function Convert(pRootOfTree)
{
if (pRootOfTree == null) return null
let stack = [];
let node = pRootOfTree
let pre = null
let head = null
while (stack.length || node) {
//一直遍历到树的最左边也就是最小值
if (node) {
stack.push(node);
node = node.left;
} else {
//最左子结点出栈
let top = stack.pop()
//pre为null说明top就是最左子结点就是链表的头
if (pre == null) {
head = top
} else { //pre不为null,那就需要top的左右指针赋值
pre.right = top
}
top.left = pre
pre = top //链表最后结点就是top了
node = top.right
}
}
return head
}
6.两个链表的公共结点
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
我的解法
双指针,长链表的指针先走,等走到剩余长度与短链表相同时,短链表的指针也开始走,往后就要判断两指针是否相同。
很明显,要获取两个链表的长度。
function FindFirstCommonNode(pHead1, pHead2)
{
if(pHead1 == null||pHead2 == null)
return null
let gap = GetListLength(pHead1)- GetListLength(pHead2)
let short = pHead1,long = pHead2
//要知道哪个是长链表
if(gap>0){
short = pHead2
long = pHead1
}
//long先走
while(gap--){
long = long.next
}
//开始一起走
while(long!=null){
if(long==short){
return long
}
long = long.next
short = short.next
}
}
//获取链表长
function GetListLength(pHead){
let cur = pHead
let len = 0
while(cur){
len++
cur = cur.next
}
return len
}