剑指offer(链表中倒数第k个节点、链表中环的入口节点、反转链表、合并两个排序的链表、树的子结构)
十、代码的鲁棒性
1. 链表中倒数第k个节点
题目描述:
输入一个链表,输出该链表中倒数第k个结点。
思路:
为了实现只遍历链表一次就能找到倒数第k个节点,我们可以定义两个指针。第一个指针从链表的第一个节点开始向前走k-1步,第二个指针则保持原地不动;从第k步开始,第二个指针也开始从链表的第一个节点开始遍历。由于两个指针的距离始终保持在k-1,当第一个(走在前面的)指针到达链表的尾节点时,第二个(走在后面的)指针恰好指向倒数第k个节点。
代码:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
//当输入的head为空指针或者k为0时,返回null
if (head == null || k == 0) {
return null;
}
ListNode ahead = head;
ListNode behind = head;
for (int i = 1; i <= k - 1; i++) {
ahead = ahead.next;
//若k的值大于链表长度时,返回null
if (ahead == null) {
return null;
}
}
while (ahead.next != null) {
ahead = ahead.next;
behind = behind.next;
}
return behind;
}
}
2. 链表中环的入口节点
题目描述:
给一个不带头结点的单链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:
先说个定理:两个指针一个fast、一个slow同时从一个链表的头部出发,fast一次走2步,slow一次走1步,如果该链表有环,两个指针必然在环内相遇,此时只需要把其中的一个指针重新指向链表头部,另一个不变(还在环内),这次两个指针一次走一步,相遇的地方就是入口节点。
代码:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode *fast=pHead,*slow=pHead;
while(fast->next!=NULL){
fast=fast->next->next;
slow=slow->next;
if(fast==slow){
break;
}
}
if(fast->next==NULL){
return NULL;
}else{
fast=pHead;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
}
};
3. 反转链表
题目描述:
输入一个链表,反转链表后,输出新链表的表头。
思路:
依次取下原链表中的每一个节点,进行头插。在取节点的过程中,要注意防止断链。
代码:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode p = head.next;
head.next = null;
while (p != null) {
ListNode q = p.next;//在每次进行头插之前,要先保存好下一个节点,防止断链
p.next = head;
head = p;
p = q;
}
return head;
}
}
4. 合并两个排序的链表
题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:
见代码。
代码:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null && list2 == null) {
return null;
} else if (list1 == null) {
return list2;
} else if (list2 == null) {
return list1;
}
ListNode head = new ListNode(-1);//新建一个头结点,用来存合并之后的链表(点睛之笔)
ListNode p = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
p.next = list1;
list1 = list1.next;
} else {
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
if (list1 != null) {
p.next = list1;
} else {
p.next = list2;
}
return head.next;
}
}
5. 树的子结构
题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
见代码。
代码:
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean result = false;
//当root1或root2中有一棵为空树时,直接返回false。
if (root1 != null && root2 != null) {
//判断当前的根节点是否和root2的根节点相同
if (root1.val == root2.val) {
//如果相同,继续判断
result = doesTree1HaveTree2(root1, root2);
}
//若找不到,去左子树继续找
if (result == false) {
result = doesTree1HaveTree2(root1.left, root2);
}
//若左子树中也找不到,则去右子树继续找
if (result == false) {
result = doesTree1HaveTree2(root1.right, root2);
}
}
return result;
}
public static boolean doesTree1HaveTree2(TreeNode root1, TreeNode root2) {
//若树root2遍历完了都能对应上,返回true
if (root2 == null) {
return true;
}
//若root2还没有遍历完,root1却遍历完了,返回false
if (root1 == null) {
return false;
}
if (root1.val != root2.val) {//如果有不相同的节点,返回false
return false;
} else {//若根节点相同,则继续判断左右子树是否相同
return doesTree1HaveTree2(root1.left, root2.left) && doesTree1HaveTree2(root1.right, root2.right);
}
}
}