链表中的倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。由头结点开始从1开始计数,尾结点就是倒数第1个结点。
结点定义
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
分析
一般的思路是:
由于是单向链表,所以先遍历链表得到其长度L,然后再遍历一次,只走L-k+1步即可得到倒数第k个结点。
但是这种方法需要遍历链表两次,如果只遍历一次,应采用以下方法:
设置两个指针,令p1指向头结点head。p1成功走k-1步之后,才让p2指向head,然后p1和p2两个指针一起向前遍历,直到p1指向尾结点。此时,p2指向的指针就是倒数第k个元素。
为提高代码的鲁棒性,还要注意以下三点:
- 如果head指针为空,代码会试图访问空指针指向的内存,程序崩溃;
- 如果链表的长度小于k,代码会崩溃;
- 如果k=0,因为k是无符号的int型,则k-1为4204067295(无符号的0x)
Java代码
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head == null || k == 0)
return null;
ListNode p1 = head;
ListNode p2 = null;
for(int i=0; i<k-1; i++){
if(p1.next != null)
p1 = p1.next;
else
return null;
}
p2 = head;
while(p1.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
}
反转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
结点定义:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
分析
假设有一条链表,现在结点h及之前的结点已经完成了反转,pNode指向结点i,首先将pNode.next用pNext保存下来,再将pNode的下一跳指向pPrev,也就是前一结点h。这样反转后的链表才不会出现断裂。
此外,为了代码的鲁棒性,还必须考虑到以下问题:
- 输入的标头指针为NULL,或者整个链表只有一个结点的情况;
- 返回的反转链表的表头应该是原链表的尾结点。
Java代码
public ListNode ReverseList(ListNode head) {
ListNode pPrev = null;
ListNode pNode = head;
ListNode pReverseHead = null;
// 链表的头指针不为空
while(pNode != null){
ListNode pNext = pNode.next;
// 循环到链表的尾结点
if(pNext == null)
pReverseHead = pNode;
// 链表长度大于等于2
pNode.next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReverseHead;
}
}
合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
结点的定义:
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
分析
现在有两个链表,链表1的头结点是p1,链表2的头结点是p2。一开始,因为1<2,所以p1作为头结点。后面也是一样,只要比较p1和p2的大小,然后把小的作为合并链表的下一个结点即可。
为提高代码的鲁棒性,应注意:
- 如果两个链表其中一个是空的,则返回另一个链表;如果两个链表都是空的,则返回空链表。
- 可使用递归的方法来处理。
Java代码
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null)
return list2;
else if(list2 == null)
return list1;
ListNode p = null;
if(list1.val <= list2.val){
p = list1;
p.next = Merge(list1.next, list2);
}
else{
p = list2;
p.next = Merge(list1, list2.next);
}
return p;
}
}
树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
树的结点定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
要查找树B是不是树A的子结构,可以分成两步:
- 在树A中找到与树B的根节点一样的结点R;
- 判断树A中以R为根结点的子树是不是包含和树B一样的结构。
如下图所示的树A和树B的结构图
首先在树A中找到根节点(黄色)和树B的根节点一样,然后比较子树。
由于左右两个孩子都和树B不同,所有不是。
接着又找到了一个和树B根节点相同的结点(黄色),经过比较发现其子树也是一样的,所以,树B是树A的子结构。
其实第一步在树A中查找和树B根结点一样的结点,就是树的遍历。树的遍历可以利用递归或者循环来实现。相对而言,递归的代码更为简洁。
第二步也可以使用递归的思路来实现:如果结点R和树B的根节点不同,则以R为根节点的子树和树B肯定不同;如果结点R和树B的根节点相同,则递归判断其左右结点的值是否相同。
Java代码
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean res = false;
if(root1 != null && root2 != null){
if(root1.val == root2.val)
res = DoesTreeAHasTreeB(root1, root2);
// 如果不是子结构,就遍历左子树,继续查找
if(!res)
res = HasSubtree(root1.left, root2);
// 如果在左子树中也没有找到子结构,在遍历右子树
if(!res)
res = HasSubtree(root1.right, root2);
}
return res;
}
public boolean DoesTreeAHasTreeB(TreeNode root1, TreeNode root2){
if(root2 == null) //树B遍历结束(无论A是否遍历结束,B都是A的子结构)
return true;
if(root1 == null) //树B没有遍历结束,但树A遍历完
return false;
if(root1.val != root2.val)
return false;
return DoesTreeAHasTreeB(root1.left, root2.left) &&
DoesTreeAHasTreeB(root1.right, root2.right);
}
}