目录
- 第一题:调整数组顺序使奇数位于偶数前面
- 第二题:链表中倒数第k个结点
- 第三题:反转链表
- 第四题:合并两个排序的链表
- 第五题:树的子结构
- 第六题:二叉树的镜像
第一题:调整数组顺序使奇数位于偶数前面
题目链接
题目:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解析:
- 这个题目和剑指Offer书上面的题目有点不同,这个题目不能改变原先的相对的顺序,要想保证原有次序,则只能顺次移动或相邻交换。。
如果可以改变原来的相对顺序,可以看这个LintCode373Partition Array by Odd and Even,这个和书上的是一样的,可以使用双指针的方法来做。 - 双指针的思路: ,一个从前面开始找,令一个从后面开始找,第一个找到一个偶数,第二个找到一个奇数,就进行互换;
LintCode373的解题代码:
public void partitionArray(int[] nums) {
int L = 0,R = nums.length-1;
while(L < R){
while(L < R && odd(nums[L])) L++;
while(L < R && !odd(nums[R])) R--;
if(L < R){
int t = nums[L];
nums[L++] = nums[R];
nums[R--] = t;
}
}
}
private boolean odd(int n){
return ( n & 1) == 1 ? true : false;
}
第二种解法模仿冒泡排序,冒泡排序是交换前面的一个数比后面的一个数大的情况,而这个题目是交换前面是偶数而后面是奇数的情况:
public void reOrderArray(int[] array) {
for(int end = array.length-1; end > 0; end--){//n-1次冒泡
for(int i = 0; i < end; i++){
if(!odd(array[i]) && odd(array[i+1])){//把偶数往后面冒
int t = array[i];
array[i] = array[i+1];
array[i+1] = t;
}
}
}
}
private boolean odd(int n){
return (n&1) == 1 ? true : false;
}
参考了牛客网问答区的解答:
要保证相对的次序不变,也可以使用下面的方法:
- L从左向右遍历,找到第一个偶数;
- R从L+1开始向后找,直到找到第一个奇数;
- 将[L,…,R-1]的元素整体后移一位,最后将找到的奇数放入L位置,然后L++;
- 如果没有找到那样的arr[R]是奇数,那说明右边没有奇数了,可以break了;
public void reOrderArray(int[] array) {
int L = 0,R;
while(L < array.length){
while(L < array.length && odd(array[L])){ // 先找到第一个偶数
L++;
}
R = L + 1;
while(R < array.length && !odd(array[R])){ // 再在L 的后面开始找到第一个奇数
R++;
}
// 注意此时arr[L]是偶数 arr[R]是奇数 -->将 [L,..R-1]中的数 向后移动一个位置
if(R < array.length){
int t = array[R];
for(int i = R-1; i >= L; i--)
array[i+1] = array[i];
array[L] = t;
L++;
}else
break;//查找失败 说明此时后面的都是偶数,可以退出了
}
}
private boolean odd(int n){
return (n&1) == 1 ? true : false;
}
至于使用额外空间的就比较简单了,就不贴代码了。。。
第二题:链表中倒数第k个结点
题目链接
题目:
输入一个链表,输出该链表中倒数第k个结点。
解析:
- 常规思路: 先遍历一遍求出链表长度,然后从头开始走len - k 个就可以到倒数k个结点;
- 这题可以设置两个指针ahead,先让第一个指针first走k-1步,然后两个指针再一起走,当第二个指针second走到末尾时,第一个指针就刚好是倒数第k个结点;
常规思路:
//常规解法
public ListNode FindKthToTail(ListNode head,int k) {
ListNode cur = head;
int len = 0;
while(cur != null){
cur = cur.next;
len++;
}
if(k > len)
return null;
cur = head;
for(int i = 0; i < len-k; i++)cur = cur.next;
return cur;
}
双指针的思路:
public ListNode FindKthToTail(ListNode head,int k) {
if(head == null || k == 0) //注意要特判
return null;
ListNode first = head,second = head;
for(int i = 1; i <= k-1; i++) // first 先走k-1步
first = first.next;
if(first == null) //k > len 特判
return null;
while(first.next != null){
first = first.next;
second = second.next;
}
return second;
}
第三题:反转链表
题目链接
题目:
输入一个链表,反转链表后,输出链表的所有元素。
解析:
这个题目可以参见这篇博客。
- 思路一,使用pre、next指针,pre指向当前cur的前一个,next是当前cur的下一个指针,然后每次都改变cur的next为pre,且循环递推知道cur = null,最后返回pre;
- 思路二,使用递归来代替循环;
public ListNode ReverseList(ListNode head) {
ListNode pre = null,cur = head,next;
while(cur != null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
使用递归:
public ListNode ReverseList(ListNode head) {
return reverse(head,null);
}
private ListNode reverse(ListNode cur,ListNode pre){
if(cur == null)
return pre;
ListNode next = cur.next;
cur.next = pre;
return reverse(next,cur);
}
第四题:合并两个排序的链表
题目链接
题目:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解析:
- 思路一: 使用类似合并有序数组的方法,外排的方式,但是这里要注意设置一个虚拟的头结点,这样的话方便第一个结点的添加和判断;
- 思路二: 使用递归的方法,先建立一个新的结点,然后比较两个链表头结点的大小,用小的那个作为头结点,然后递归小的那个链表的下一个结点和另一个表的结点递归比较,让较小的结点作为上一个新结点的下一个结点;
类似合并数组外排的方式:
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null)
return list2;
if(list2 == null)
return list1;
ListNode p1 = list1,p2 = list2;
ListNode dummyHead = new ListNode(-1);
ListNode p3 = dummyHead;
while(p1 != null && p2 != null){
if(p1.val < p2.val){
p3.next = p1;
p1 = p1.next;
}else {
p3.next = p2;
p2 = p2.next;
}
p3 = p3.next;
}
while(p1 != null){
p3.next = p1;
p1 = p1.next;
p3 = p3.next;
}
while(p2 != null){
p3.next = p2;
p2 = p2.next;
p3 = p3.next;
}
return dummyHead.next;
}
因为是链表结构,所以最后的合并只需要一步即可,也就是不需要while循环:
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null)
return list2;
if(list2 == null)
return list1;
ListNode p1 = list1,p2 = list2;
ListNode dummyHead = new ListNode(-1);
ListNode p3 = dummyHead;
while(p1 != null && p2 != null){
if(p1.val < p2.val){
p3.next = p1;
p1 = p1.next;
}else {
p3.next = p2;
p2 = p2.next;
}
p3 = p3.next;
}
if(p1 != null)
p3.next = p1;
if(p2 != null)
p3.next = p2;
return dummyHead.next;
}
递归:
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null)
return list2;
if(list2 == null)
return list1;
ListNode curHead = null;//当前选中的头结点
if(list1.val < list2.val){
curHead = list1;
curHead.next = Merge(list1.next,list2);
}else {
curHead = list2;
curHead.next = Merge(list1,list2.next);
}
return curHead;
}
第五题:树的子结构
题目链接
题目:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解析:
- 先在A里面找到B的根的值(某个结点A.val = B.val),然后看看子树是不是都相同(具体来说不是相同,而是A是否包含(>=B)),具体是另一个函数AcontainsB来判断;
- 如果上述条件不满足,递归在A.left或A.right中找这个值,然后再递归看子树是不是满足AcontiansB;
- 注意函数AcontainsB中,递归条件root2只要达到空,就说明找到了,返回true,反之,root1达到空,返回false,而不是判断两个树完全相等(顺便附上这个函数);
// 判断root2是不是root1的子结构
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1 == null || root2 == null)
return false;
boolean res = false;
if(root1.val == root2.val)
res = AcontainsB(root1,root2);
if(!res)
res = HasSubtree(root1.left,root2);//左边有可能包含root2
if(!res)
res = HasSubtree(root1.right,root2);//右边也有可能包含root2
return res;
}
//注意不是判断两棵树是不是完全相等,而是判断A是否包含B
private boolean AcontainsB(TreeNode A,TreeNode B){
if(B == null)// B遍历完了 说明可以
return true;
if(A == null)
return false;
// A != null && B != null
return A.val == B.val && AcontainsB(A.left,B.left) && AcontainsB(A.right,B.right);
}
判断两棵树是否完全一样:
public boolean is_SameTree(Node T1,Node T2) {
if(T1 == null && T2 == null)
return true;
else {
return T1 != null && T2 != null && T1.val == T2.val
&& is_SameTree(T1.left,T2.left) && is_SameTree(T1.right,T2.right);
}
}
第六题:二叉树的镜像
题目链接
题目
操作给定的二叉树,将其变换为源二叉树的镜像。如下图:
解析:
题目很简单,递归求解的过程:
- 先把当前根节点的左右子树换掉;
- 然后递归换自己的左右子树;
public void Mirror(TreeNode root) {
if(root == null)
return ;
TreeNode t = root.left;
root.left = root.right;
root.right = t;
Mirror(root.left);
Mirror(root.right);
}