题目14:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶说位于数组的后半部分。
分析:
最简单的思路是从头到尾扫描这个数组,每碰到一个偶数,就拿出这个偶数,并把后面所有的数字向前移动一位,最后把这个偶数放至数组末尾。由于每碰到一个偶数就需要移动O(n)个数字,因此总的时间复杂度是O(n^2)。
另一种做法是:发现偶数在奇数的前面,则交换他们的位置。可维护两个指针,第一个指针指向数组的第一个元素,它只向后移动;第二个指针指向数组的最后一个元素,它只向前移动。在两个指针相遇指针之前,第一个指针总是位于第二个指针之前,如果第一个指针指向的数字是偶数,且第二个指针指向的数字是奇数,就交换两个数字的位置。过程如下所示:
考虑使用位运算,奇数&1必然=1,偶数&1必然等于0,以此区分是奇数还是偶数。代码如下所示:
public static void reorderAddEven(int[] array) {
if (array == null || array.length == 0) return;
int i = 0, j = array.length - 1, temp;
while (i < j) {
while (i < j && (array[i] & 1) == 1) i++;
while (i < j && (array[j] & 1) == 0) j--;
if (i < j) {
temp = array[i];
array[i] = array[j];
array[j] = array[temp];
}
}
for (int k = 0; k < array.length; k++) {
System.out.print(array[k] + " ");
}
System.out.println();
}
public static void main(String[] args) {
reorderAddEven(new int[]{2, 4, 6, 1, 2, 4, 6, 7, 9, 84, 3});
reorderAddEven(new int[]{2, 4, 6, 8, 4, 1, 3, 5, 7, 9});
reorderAddEven(new int[]{2});
reorderAddEven(new int[]{});
}
题目15:输入一个链表,输出链表中倒数第k个结点,为了符合大多数人的习惯,本体从1开始计数,即链表的尾结点是倒数第1个结点。链表定义如下
public class Node<T> {
public T item;
public Node<T> next;
public Node() { }
public Node(T item) {
this.item = item;
}
}
分析:
最直观的想法是先走到链表的尾端,再从尾端回溯k步,但是此链表是单向的,因此这种思路行不通。假设整个链表有n个结点,那么倒数第k个结点就是从头结点开始往后走n-k+1个结点。要得到链表的总结点数,需要从头开始遍历一遍链表,然后再从头走n-k+1个结点到达倒数第k个结点。需要遍历链表两遍。
实际上只要遍历一遍就可以了。可以定义两个指针,第一个指针从链表的头结点开始向前走k-1步,第二个指针保持不动;从第k步开始,第二个指针也开始从链表的头结点开始遍历。由于两个指针的距离保持在k-1,当第一个指针达到链表的尾结点时,第二个指针正好在倒数第k个结点。过程示意图如下所示。
如图所示,第一个指针从头结点在链表上向前走2(3-1)步到达第3个结点,接着让两个指针同时向后遍历,当第一个指针到达链表尾结点时,第二个指针刚好在倒数第3个结点。
在写代码时需要注意以下几点:
1)输入为头结点为空处理。
2)输入的链表总结点数少于k,在头结点向前走k-1步时会造成空指针异常。因此在循环的时候可能会出现next为null的情况。
3)输入参数k为0,即输出倒数第0个结点。由于我们设计的是从1开始的,所有0是没有意义的。
代码如下所示:
public static Node findKthToTail(Node pNode, int k) {
if (pNode == null || k == 0) return null;
Node headANode = pNode;
Node headBNode = null;
for (int i = 0; i < k - 1; i++) {
if (headANode.next != null)
headANode = headANode.next;
else
return null;
}
headBNode = pNode;
while (headANode.next != null) {
headANode = headANode.next;
headBNode = headBNode.next;
}
return headBNode;
}
public static void main(String[] args) {
Node<Integer> a = new Node<>(1);
Node<Integer> b = new Node<>(2);
Node<Integer> c = new Node<>(3);
Node<Integer> d = new Node<>(4);
Node<Integer> e = new Node<>(5);
Node<Integer> f = new Node<>(6);
a.next = b;
b.next = c;
c.next = d;
d.next = e;
e.next = f;
// 头结点
// System.out.println(findKthToTail(a,6).item);
// 尾结点
// System.out.println(findKthToTail(a,1).item);
// 中间结点
// System.out.println(findKthToTail(a,3).item);
// null结点
// System.out.println(findKthToTail(null,3));
// k大于链表结点数
// System.out.println(findKthToTail(a,7));
// k==0
System.out.println(findKthToTail(a, 0));
}
变型1:求链表的中间结点。如果链表中节点数为奇数,返回中间结点;如果结束总数为偶数,返回中间两个结点的任意一个。
分析:
可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步,当走的快的指针到达链表尾结点时,走的慢的指针正好在链表的中间。
变型2:判断一个单项链表是否形成了环形结构。
分析:
定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步,如果走的快的指针追上了走得慢的指针,那么链表就是环形链表;如果走的快的指针到达链表的末尾都没有追上走的慢的指针,那么就不是环形链表。
tips:当使用一个指针不能解决问题时,可以尝试使用两个指针,可以让其中一个指针遍历的速度快一点,或者让它先在链表上走若干步。