-
剑指offer所有题目详解,可访问我的github项目:KongJetLin-offer
-
目录
- Number16:合并2个排序的链表
- Number17:树的子结构
- Number18:二叉树的镜像
- Number19:顺时针打印矩阵
- Number20:包含min函数的栈
题目16 合并2个排序的链表
题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
可以使用递归法或者排序法来完成,代码如下:
递归法
//1、递归法
//这个方法的含义是,构建以结点 list1与list2 中值较小的为头结点的链表,
// 并将list1链表以及list2链表剩下的结点按从小到大的顺序连接在该头结点后面,返回该链表
public ListNode Merge(ListNode list1,ListNode list2)
{
//首先,如果list1已经为null,返回list2;如果list2也为null,返回list1(null),当然此处也可以返回null
if(list1 == null)
return list2;
if(list2 == null)
return list1;
//找出list1与list2中值较小那个,将其作为新链表头结点(假设是list1),那么继续寻找 list1.next 与 list2中较小的结点,接到list1后面
//最后返回list1,以list1为头结点的链表就会接到上一层较小链表的后面(自己画个图就明白)
if(list1.val < list2.val)
{
list1.next = Merge(list1.next , list2);//寻找 list1.next 与 list2中较小的结点,接到list1后面
return list1;//返回list1,以list1为头结点的链表就会接到上一层较小链表的后面
}
else
{
list2.next = Merge(list1 , list2.next);
return list2;
}
}
迭代法
//2、迭代法
public ListNode Merge1(ListNode list1,ListNode list2)
{
//首先,构造一个虚拟头结点,这个头结点用于连接最后要返回的链表,值为-1
ListNode head = new ListNode(-1);
//其次,我们必须要创建一个指针指向虚拟头结点,这个指针向下移动寻找list1与list2中较小的一个并将其连接到cur上,
//由于cur与head原来指向同一个结点,那么连接到cur.next的结点,同样是连接到以head为头结点的链表上(画个图就明白)
//本来使用head就够了,但是最后要返回链表头结点,因此只能再创建一个cur指针来移动。
ListNode cur = head;
//当list1与list2 不全为null的时候,我们寻找其中较小的一个,连接到cur上,并将cur后移一位
while(list1 != null && list2 != null)
{
if(list1.val < list2.val)
{
cur.next = list1;
// cur = cur.next;
list1 = list1.next;//同时,由于list1已经添加到cur链表,我们将其后移一位继续添加
}
else
{
cur.next = list2;
// cur = cur.next;//这句可以直接放到外面
list2 = list2.next;
}
cur = cur.next;
}
//当list1与list2中一个为null,跳出循环
if(list1 != null)//如果是list2为null跳出循环,list1不为null,将list1剩下的结点接到cur后面
cur.next = list1;
if(list2 != null)//如果是list1为null跳出循环,list2不为null,将list2剩下的结点接到cur后面
cur.next = list2;
//不会出现list1与list2都为null的情况
return head.next;//通过cur指针将结点连接到以head为头结点的链表上,返回head.next为头结点的链表即可!
}
题目17 树的子结构
题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
分析:
第一步在树A中查找与B根节点一样的节点,实际上就是树的遍历,可以采用递归的方式,即对比A当前结点与B根结点是否相同,不同就递归判断 A的左右孩子结点与B的根结点是否相同。
第二步,如果找到A中某个结点node与B的根结点值相同,则递归地判断它们各自的左右孩子节点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶节点。
代码如下:
//这个方法用于判断 树root2是不是树root1的子结构(子树)
public boolean HasSubtree(TreeNode root1,TreeNode root2)
{
/*
进方法的时候,有几种情况:
1)root1=null,root2=null/root1!=null,root2=null,由于约定空树不是任意一个树的子结构
即root2进来就是null,那么直接返回false;
2)root1=null,root2!=null,说明root1递归到叶子结点,但是root2还有下面的结点,同样不满足,返回false
即只有 root1,root2均不为null的时候,才需要继续判断;揉揉腿1或者root2有一个是null,终止判断返回false
*/
if(root1 == null || root2 == null)
return false;
//定义变量flag,用于保存最后的结果
boolean flag = false;
//对比A当前结点与B根结点是否相同,相同调用 isSubTree() 方法递归地判断它们各自的左右孩子节点的值是不是相同
if(root1.val == root2.val)
{
//判断 以root2为根的树 是不是 以root1为根的树 的子树,保存判断结果
flag = isSubTree(root1 , root2);
}
//如果当前 以root2为根的树 不是 以root1为根的树 的子树,flag=false,
// 递归判断 树root2是不是 树root1.left 或者 树root1.right 的子结构,保存递归结果。(此处 树root1 表示以root1为根的子树)
if(!flag)
{
//只要有一个子结构满足即可,因此用“或”
flag = HasSubtree(root1.left , root2) || HasSubtree(root1.right , root2);
}
return flag;
}
//这个方法用于判断根值相同的2个树node1,node2,node2是不是node1的子树
private boolean isSubTree(TreeNode node1 , TreeNode node2)
{
/*
遍历到最后有几种情况:
1)node1!=null,node2=null,说明node2递归到底端,但是node1没有到底端,前面父结点满足,返回true;
2)node1=null,node2=null,说明node1,node2递归到底端,前面父结点满足,返回true;
3)node1=null,node2!=null,说明说明node1递归到底端,但是node2还没,那么不满足,返回false
4)node1!=null,node2!=null,继续判断。
即,只要node2=null,返回true;node1=null,node2!=null,返回false,否则继续执行
*/
if(node2 == null)
return true;
if(node1==null && node2!=null)
return false;
//如果node1,node2值相同,继续递归判断他们对应的子结点是否相同
if(node1.val == node2.val)
{
//左右子树必须全部相同才可以返回true,用“与”
return isSubTree(node1.left , node2.left) && isSubTree(node1.right , node2.right);
}
else
{
//当有一个结点值不同,说明当前子结构不满足,返回false
return false;
}
}
题目18 二叉树的镜像
题目描述:操作给定的二叉树,将其变换为源二叉树的镜像。
分析:我们从二叉树的根结点开始,把当前结点的左右孩子结点交换,这样当前结点的左右子树就完成镜像,但是左右子树的子树还没有完成镜像,因此我们接下来通过递归的方式,将左右孩子结点的左右子树进行交换…直到当前结点为null,结束递归!
代码如下:
public void Mirror(TreeNode root)
{
//如果当前结点为null,那么当前结点就没有左右孩子结点,不需要镜像
if(root == null )
return;
else
{//当当前结点不为null,如果当前结点的左右孩子结点都为null,也不需要镜像(但是其实镜像也没有关系,如果判断,最优的情况下可以减少接近一半的镜像!)
if((root.left==null && root.right==null))
return;
}
//先将当前结点的左右子树交换
swap(root);
//下面交换左右孩子结点的左右子树
Mirror(root.left);
Mirror(root.right);
}
private void swap(TreeNode node)
{
TreeNode temp = node.right;
node.right = node.left;
node.left = temp;
}
题目19 顺时针打印矩阵
题目描述:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
分析:从外圈到内圈的顺序依次打印的,每次打印一个圈的数字。
下来分析如何打印一圈,将打印一圈分为4步:
1)从左到右打印一行
2)从上到下打印一列
3)从右到左打印一行
4)从下到上打印一列
每一步根据起始坐标和终止坐标来用一个循环即能实现。
分析打印时每一步的前提条件:
第一步是必须的,因为打印一圈至少有一步;如果只有一行,那么就不需要第二步了,也就是说第二步的前提条件是终止行号大于起始行号;需要第三步的前提条件是圈内至少有两行两列,也就是说除了终止行号大于起始行号,还要求终止列号大于起始列号;第四步的前提条件是至少有三行两列,因此要求终止行号比起始行号至少大2,同时终止列号大于起始列号。
代码如下:
public static ArrayList<Integer> printMatrix(int [][] matrix)
{
ArrayList<Integer> arrayList = new ArrayList<>();
//在行、列分别定义2个指针,指向行列的首尾
int r1 = 0 , r2 = matrix.length-1;
int c1 = 0 , c2 = matrix[0].length-1;
//当 r1>r2 且 c1>c2的时候,这时候所有的圈都遍历完了,结束遍历(这种情况下,最后一圈只有一个数字,即r1=r2 且 c1=c2的情况也考虑到)
while(r1<=r2 && c1<=c2)
{
//从左到右是必须的
for (int i = c1; i <= c2 ; i++)
{
arrayList.add(matrix[r1][i]);
}
//当终止行号大于起始行号,从上到下
if(r2 > r1)
{
for (int i = r1+1; i <= r2 ; i++)
{
arrayList.add(matrix[i][c2]);
}
}
//除了终止行号大于起始行号,还要求终止列号大于起始列号,从右到左
if(r2 > r1 && c2 > c1)
{
for (int i = c2-1; i >= c1 ; i--)
{
arrayList.add(matrix[r2][i]);
}
}
//终止行号比起始行号至少大2,同时终止列号大于起始列号
if(c2 > c1 && r2-2 >= r1)
{
for (int i = r2-1; i > r1 ; i--)
{
arrayList.add(matrix[i][c1]);
}
}
//最后,遍历完一圈记得调整行列首尾指针
r1++; r2--; c1++; c2--;
}
return arrayList;
}
题目20 包含min函数的栈
题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:
1)保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法;
2)min方法的时间复杂度应该为O(1)。
如果只使用一个数据结构实现,需要每次压入一个新元素进栈时,将栈里的所有元素排序,让最小的元素位于栈顶。但是这种想法不能保证最后压入栈的元素能够最先出栈,因为这个数据结构已经不是栈了。
我们创建2个Stack,一个dataStack正常入栈出栈元素,另一个minStack保存最小的元素。每次压入一个新元素进栈的时候,如果该元素比当前最小的元素还要小,则minStack更新最小元素,dataStack也添加一个元素;由于每次dataStack出栈的时候,为了保证元素的顺序性,minStack也要出栈栈顶元素,否则单单是dataStack出栈而minStack不出栈,2个栈元素的顺序性不同。
因此,对于minStack,它的栈的元素个数应该与dataStack保持相同。如果先插入的元素小于栈顶元素(栈顶元素最小),我们将元素入栈到栈顶,如果新插入的元素大于等于栈顶元素,我们就再次存入栈顶元素。 这样,我们每次取min的时候,都能保证取到当前序列的最小元素,因为此时取到栈顶元素,而minStack栈其他元素都小于或者等于栈顶元素,必然可以保证取到的元素最小!
代码如下:
private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int node)
{
//入栈的时候,2个栈都入栈
dataStack.push(node);
//minStack为空,直接入栈node,否则入栈node与栈顶元素较小的一个。
minStack.push( minStack.isEmpty() ? node : Math.min(node , minStack.peek()));
}
public void pop()
{
if(dataStack.size() > 0 && minStack.size() > 0)
{
//当2个栈不为空,才可以出栈元素(其实2个栈元素个数一样,判断一个即可)
dataStack.pop();
minStack.pop();
}
}
public int top()
{
return dataStack.peek();//直接读取dataStack栈顶元素即可(不会讲dataStack栈顶元素弹出,只是读取)
}
public int min()
{
return minStack.peek();//直接读取minStack栈顶元素即可(不会讲mintack栈顶元素弹出,只是读取)
}