剑指offer所有题目详解,可访问我的github项目:KongJetLin-offer
目录
题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
//删除以 pHead 为根结点的
public ListNode deleteDuplication(ListNode pHead)
{
/*
1)当链表第一个结点为null,直接返回这个结点
2)当遍历到链表的最后一个结点,下一个结点为null,此时不会有与他相同的下一个结点,返回这个最后的结点
注意,必须要判断 pHead.next 不为null,这是下面操作的前提!!
*/
if(pHead == null || pHead.next == null)
return pHead;
//当 pHead.next 存在的时候,将其取出进行判断(这里 pHead.next存在 是下面操作的前提)
ListNode nextNode = pHead.next;
//当 pHead.val == nextNode.val 的时候,我们需要删除pHead结点以及后面值与他相同的结点
if(pHead.val == nextNode.val)//nextNode不存在的时候,这里会报空指针异常,因此前面必须要排除 nextNode不存在的情况
{
//如果 pHead.val == nextNode.val,我们持续将 nextNode 的指针后移,直到 nextNode=null 或者 pHead.val != nextNode.val
while(nextNode != null && pHead.val == nextNode.val)
{
nextNode = nextNode.next;
}
//注意,由于 pHead 的值与nextNode 的值相同,pHead 也必须删除
//当 nextNode=null 的时候,deleteDuplication(pHead) 返回 null,正确;
//当 nextNode!=null 的时候,deleteDuplication(pHead) 返回以nextNode为头结点的删除了重复元素的链表,正确
return deleteDuplication(nextNode);//这样返回就将 pHead 也删除了
}
else
{
//当 pHead.val != nextNode.val 的时候,我们不需要删除pHead,删除以 pHead.next为根的链表的重复结点并返回
pHead.next = deleteDuplication(pHead.next);//注意将pHead连接到返回的链表上
return pHead;
}
}
题目描述:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析:结合图,我们可发现分成两大类:
1)有右子树的,那么下个结点就是右子树最左边的点(eg:D,B,E,A,C,G);
2)没有右子树的,也可以分成两类:
a)是父节点左孩子(eg:N,I,L) ,那么父节点就是下一个节点 ;
b)是父节点的右孩子(eg:H,J,K,M),找他的父节点的父节点的父节点…直到当前结点是其父节点的左孩子位置,那么当前结点的父结点就是我们要找的当前结点的下一个结点;如果遍历到根结点都没有找到,那么说明当前结点已经是最后一个结点。
为什么是这种规律?根据中序遍历特性,左父结点一定已经被中序遍历访问过,所以下一个结点一定是在父节点路径上的第一个右父节点。停车不管是情况a还是情况b,我们都需要向上,直到找到当前结点的父节点在右边,也就是说当前结点是其父节点的左孩子结点,那么其父节点就是我们想要寻找的下一个结点。
参考:添加链接描述
代码如下:
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
//首先,如果pNode=null,说明当前结点不存在,那么下一个结点肯定也不存在,直接返回null
if(pNode == null)
return null;
//第一种情况,pNode存在右子树,找到右子树最左边的结点
if(pNode.right != null)
{
pNode = pNode.right;//将指针移动到右子树根结点
if(pNode.left != null)
pNode = pNode.left;//一直将指针移动到当前结点的左孩子结点,直到左孩子结点不存在,那么当前结点就是下一个结点(右子树的最左结点)
return pNode;
}
//第二种情况,pNode不存在右子树,那么一直找他的父节点,直到当前结点是其父结点的左孩子结点(父节点是当前结点的右父节点)
//如果父亲结点存在,一直寻找符合条件的父亲结点
while(pNode.next != null)
{
if(pNode.next.left == pNode)
return pNode.next;//找到“右父节点”,那么这个“右父节点”就是下一个结点
pNode = pNode.next;//没有找到“右父节点”,就一直将指针移动到父节点向上寻找
}
//如果遍历到父亲结点不存在还是没有满足条件的结点,说明pNode是最后一个结点你,直接返回null
return null;
}
题目描述:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
分析:首先分析下这个对称二叉树,也就是一个二叉树中间对称。所以我们可以使用递归的思想,首先以根节点以及其左右子树,左子树的左子树和右子树的右子树相同,左子树的右子树和右子树的左子树相同。两个条件都要符合,所以我们第一个传根节点的左子树和右子树,先判断左右子树根结点的比较。然后分辨对左子树的左子树和右子树的右子树。左子树的右子树和右子树的左子树进行判断。只有两个条件都满足则返回的是true,一层一层递归进入,则可以得到结果。
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot == null)
return true;//如果根结点为null,表示二叉树是对称的,返回true
return isSymmetrical(pRoot.left , pRoot.right);
}
//用于比较 以node1为根的左子树 与 以node2位根的右子树 的对应位置的结点是否全部相等,返回比较的Boolean结果
private boolean isSymmetrical(TreeNode node1 , TreeNode node2)
{
//---------------------------------------首先比较2个子树的根结点
//如果node1与node2都是null,说明到了二叉树的末尾,此时应该返回true
if(node1 == null && node2 == null)
return true;
//如果node1与node2不都是null,但是node1或者node2有一个是null,此时说明二叉树不是对称的
if(node1 == null || node2 == null)//前面已经把node1与node2都是null的情况排除,现在这里只有可能出现 node1或者node2有一个是null
return false;
//如果 node1与node2都存在,且他们的值不相等,二叉树不对称,返回false
if(node1.val != node2.val)
return false;
//-----------------------------------------其次比较2个子树的子树对应位置的结点是否相等
//如果2个结点相等,且还没有到达二叉树末尾,那么比较 以node1的左孩子结点为根的树 与 以node2右孩子结点为根的树 是否相等,
//以及 以node1右孩子结点为根的树 与 以node2的左孩子结点为根的树 是否相等,只有他们全部相等才会返回true(这部分画个图就知道了)
return isSymmetrical(node1.left , node2.right) && isSymmetrical(node1.right , node2.left);
}
题目描述:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
分析:这题类似 60 题,不管这题需要按照不同的行采取从左到右或者从右到左的打印方式。相对于60题,我们添加一个变量来确定这一行的打印方式,使用 Collections.reverse 方法来翻转存储某一行元素的 ArrayList。
代码如下:
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
{
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>();
//注意,这一题如果pRoot为null的时候,我们直接返回arrayList即可
//否则因为根结点为空,queue.add(pRoot) 的时候回出现空指针异常
if(pRoot == null)
return arrayLists;
//创建一个队列用于存储结点
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);//先将根结点存储进来
boolean reverse = false;//创建一个变量判断反转,第一行从左到右不需要反转,设置初始值为false
while(!queue.isEmpty())
{
ArrayList<Integer> temp = new ArrayList<>();//用于存储某一行结点值的临时ArrayList
//用一个变量记录队列保存的上一层的存储到队列中的结点值的数量
//这里必须将上一层存储的结点数量先取出来,不能一边减少一边取,因为我们在取出的上一层结点值同时,这一层的结点值也在存储进去
int count = queue.size();
while (count>0)
{
TreeNode node = queue.remove();
temp.add(node.val);
count--;
//取出上一层的某一个结点后,如果这个结点右左右孩子结点,这些孩子结点属于这一层,将他们存储到队列中
if(node.left != null)
queue.add(node.left);
if(node.right != null)
queue.add(node.right);
}
if(reverse)
Collections.reverse(temp);
reverse = !reverse;//将标志反转
arrayLists.add(temp);
}
return arrayLists;
}
题目描述:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
注意区分于第22题:从上往下打印二叉树,第22题的题目要求是:从上往下打印出二叉树的每个节点,同层节点从左至右打印。这里虽然也是从上往下打印二叉树每个节点,队首要求每一层节点输出为一行,也就是说,我们必须把在同一层的结点打印完后,再打印其他层的结点。其实方法与22题差不多,如下:
/**这一题注意对比第22题的解法*/
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
{
ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>();
//注意,这一题如果pRoot为null的时候,我们直接返回arrayList即可
//否则因为根结点为空,queue.add(pRoot) 的时候回出现空指针异常
if(pRoot == null)
return arrayLists;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);
while(!queue.isEmpty())
{
//用一个变量记录队列保存的上一层的结点的数量
int count = queue.size();
ArrayList<Integer> temp = new ArrayList<>();
//把上一层放入队列的结点的值全部取出放入ArrayList,同时将这些结点的左右孩子结点放入队列
while(count>0)
{
TreeNode cur = queue.remove();
temp.add(cur.val);
count--;//每取出一个count-1,这样就能保证不打印这一层放入的新的结点
//将队列取出的结点的孩子结点放入队列,这些结点是下一层的结点,用于下一层的打印
if(cur.left != null)
queue.add(cur.left);
if(cur.right != null)
queue.add(cur.right);
}
//将添加了这一层所有结点的ArrayList放入最大的ArrayList
arrayLists.add(temp);
}
//添加完所有ArrayList以后,将最大的ArrayList返回
return arrayLists;
}