题目11:数值的整数次方
书中方法:这道题要注意底数为0的情况。double类型的相等判断。乘方的递归算法。
public double power(double base, int exponent){
//指数为0
if(exponent == 0){
return 1.0;
}
//底数为0
if(isEqual(base, 0.0)){
return 0.0;
}
int absExponent = exponent;
if(exponent < 0)absExponent = -absExponent;
double result = unsignedPower(base, absExponent);
if(exponent<0){
result = 1.0/result;
}
return result;
}
private double unsignedPower(double base, int absExponent){
double result = 1.0;
for(int i=1; i<=absExponent; i++){
result *= base;
}
return result;
}
//用递归的方法求乘方
private double unsignedPower2(double base, int absExponent){
if(absExponent == 1)return base;
double result = unsignedPower2(base, absExponent>>1);
result *= result;
if((absExponent & 0x01) == 1){
result *= base;
}
return result;
}
//double类型判断相等
private boolean isEqual(double num1, double num2){
if((num1 - num2)>-0.0000001 && (num1 - num2)<0.0000001){
return true;
}else{
return false;
}
}题目12:打印1 到 最大的n位数。
书中方法:这道题的一个陷阱在于不能用int或者long去存储你要打印的数,然后用打印函数打印,因为这个数可能会很大。如果加1后超出了最大的n位数,就不打印了。用最高位是否进位判断是否结束,打印的时候注意不要打印出前面可能出现的0.
public void print(int n){
if(n<=0){
return;
}
//必须要用字符数组防止大数
char[] c = new char[n];
for(int i=0; i<n; i++){
c[i] = '0';
}
while(!increment(c)){
digitsPrint(c);
}
}
private boolean increment(char[] c){
boolean overflow = false;
//因为是加1,当作从小数进位了1.
int carry = 1;
//从个位开始计算
for(int i=c.length-1; i>=0; i--){
int sum = c[i] - '0' + carry;
//如果进位了
if(sum == 10){
if(i == 0){
overflow = true;
break;
}
carry = 1;
c[i] = '0';
}else{//如果没进位
c[i] = (char)('0' + sum);
break;
}
}
return overflow;
}
private void digitsPrint(char[] c){
int index = 0;
while(c[index] == '0'){
index++;
}
for(int i=index; i<c.length; i++){
System.out.print(c[i]);
}
System.out.println();
}我的方法:看到“所有”和“打印”这样的关键字,很容易想到用回溯的方法去做,因为实质是求出所有组合,上面的方法也是为了能遍历到所有组合。和求字符串的全排列以及组合不同(题目28),这里字符可以重复使用(又联想到了打印n位以内数字不重复整数…)。回想一下我们打印字符串的全排列时,因为每个字符只能使用一次,所以我们得想办法保证每个字符只被选取一次(利用额外的数组或者交换)。现在我们只需要简单地在每个位子上选取可能出现的值,然后递归下去就行了。外层是一个控制长度的循环,内层为每一位选取数字,每一位上都有‘0’-‘9’字符可以选择(第一位除外)。
public void print2(int n){
if(n <= 0)return;
List<String> result = new ArrayList<String>();
String line = "";
//用n控制位数
for(int i=1; i<=n; i++){
find(result, line, 0, i);
}
for(String s : result){
System.out.println(s);
}
}
private void find(List<String> result, String line, int level, int border){
//每一位添加完毕后保存
if(level >= border){
result.add(new String(line));
return;
}
String temp = new String(line);
for(int i=0; i<=9; i++){
//第一位不能为0
if(level == 0 && i == 0){
continue;
}else{
line += i;
find(result, line, level+1, border);
line = temp;
}
}
}题目13:在O(1)时间删除链表节点。
public ListNode delete(ListNode head, ListNode toBeDelete){
//如果头节点为空或者只有一个节点
if(head == null || head.next == null)return null;
//如果要删除的节点在末尾
if(toBeDelete.next == null){
ListNode index = head;
while(index.next!= toBeDelete){
index = index.next;
}
index.next = index.next;
}else{//要删除的节点不在末尾
toBeDelete.val = toBeDelete.next.val;
toBeDelete.next = toBeDelete.next.next;
}
return head;
}题目14:输入整数数组,使所有奇数位于前半部分,所有偶数位于后半部分。
我的方法:想到用两个下标分别表示奇数和偶数的界线,一个在开头,一个在末尾,判断每一个数字的类别,然后将它放入对应的范围内,移动下标,直至两个下标相遇。两个下标,第一个index表示当前要检测的数字以及其左边的数字为奇数(所以当前要检测的数字为奇数的时候,index才会向右移动,否则是even向左移动),even表示其右边的数字为偶数,当index 大于 even的时候分割完毕。这里有个细节,在index表示index左边是奇数,even表示even右边是偶数的前提下 ,如果将while中的“<=”换成“<”(也就是index和even相遇时终止循环,相遇的这个元素无论偶奇不影响分割)也可以完成分割,但此时不知道最后一个元素到底是奇还是偶,如果while中的条件是“<=”(也就是index == even+1时终止循环),我们就可以知道确切的奇偶分界,这一点也可以利用到快速排序中。

public class PartitionInArray {
public void partition(int[] a){
if(a == null || a.length == 0)return;
int index = 0;
int even = a.length - 1;
while(index <= even){
if((a[index] & 0x01) == 1){
index ++;
}else{
exch(a, index, even);
even--;
}
}
}
private void exch(int[] a, int l, int m){
int temp = a[l];
a[l] = a[m];
a[m] = temp;
}
}书中的方法:指针odd(odd左边为奇数)指向开头,指针even(even右边为偶数)指向末尾。同时移动,直到两指针“相遇”(书中是相遇就结束,这里我依旧用odd超过even结束)。
public void partition2(int[] a){
if(a == null || a.length == 0)return;
int odd = 0;
int even = a.length-1;
while(true){
while(odd <= even && isOdd(a[odd])){
odd++;
}
while(odd <= even && !isOdd(a[even])){
even--;
}
if(odd - even == 1){
break;
}
exch(a, odd, even);
}
}
private boolean isOdd(int m){
if((m & 0x01) == 1){
return true;
}
return false;
}联想:这里很自然地就想到了快速排序的分割——将数组分割成小于目标数字和大于等于目标数字两块,只需要把上述上述中的判断条件改了,同时选取一个用于分割的标准元素即可。
题目15:链表中的倒数第k个节点。
书中方法:用两个节点一次遍历求得倒数第k个节点。注意头节点为空,k<=0,k大于节点个数的情况。
public ListNode find(ListNode head, int k){
if(head == null || k <=0){
return null;
}
ListNode first = head, second = head;
for(int i=1; i<=k; i++){
//如果k超出了节点的个数
if(first == null){
return null;
}else{
first = first.next;
}
}
while(first != null){
first = first.next;
second = second.next;
}
return second;
}题目16:输入链表的头节点,反转链表。
书中方法:对于一个链表,我们只能从头往后遍历,如果要反转,我们需要更改当前节点的next域指向前一个节点,此时链表断开,为了能继续修改下一个节点的next域,我们还要维护下一个节点。
public ListNode reverse(ListNode first){
if(first == null)return first;
ListNode last = null;
ListNode now = first;
ListNode next = first.next;
while(now != null){
now.next = last;
last = now;
now = next;
if(now != null){
next = now.next;
}
}
return last;
}方法二:书后面还提到了递归的方法。自己写的逻辑比较不清楚,在网上找了一个版本。这个方法的思路是:递归返回的是当前节点右侧已经反转好的链表的头节点,对于当前的节点,将它连接到已经reverse好的链表的末尾,返回值是添加了该节点的新链表头。先递归后处理,最后的返回值是反转后的节点,每次递归返回的是添加了当前节点的反转后的链表。注意递归出口的处理。
public ListNode reverse2(ListNode first){
if(first == null || first.next == null)return first;
ListNode newHead = reverse2(first.next);
first.next.next = first;
first.next = null;
return newHead;
}题目17:合并两个排序链表。
我的方法:新初始化一个链表头,比较两个链表当前节点的大小,然后连接到该链表中。遍历两个链表直到null为止。
public ListNode merge(ListNode first, ListNode second){
//注意这个细节
ListNode head = new ListNode(0);
ListNode index = head;
while(first != null && second != null){
if(first.val < second.val){
index.next = first;
first = first.next;
}else{
index.next = second;
second = second.next;
}
index = index.next;
}
if(first != null)index.next = first;
else index.next = second;
return head.next;
}书中方法:我们每一次都是找两个链表值中较小的作为结果节点,它的下一个节点依旧会重复一样的过程,这是典型的递归过程,可以得到以下的递归方法。先处理后递归,最后的返回值是头节点,每次递归的返回值是较小的节点。
public ListNode merge2(ListNode first, ListNode second){
if(first == null)return second;
if(second == null)return first;
ListNode nowHead = null;
if(first.val < second.val){
nowHead = first;
nowHead.next = merge2(first.next, second);
}else{
nowHead = second;
nowHead.next = merge2(first, second.next);
}
return nowHead;
}题目18:输入两棵二叉树A和B,判断B是不是A的子结构。(补充下,根据书中的代码来看,子结构的定义并不包括叶子节点下的null,也就是说只要B的存在数字的结构存在于A中就行,那么如果B是null树,那么就不属于A的子结构)
书中方法:书上的方法十分清晰,分为两个步骤,先在A树中找到B树的root.val,然后判断由该点向下是否完全包含B树的结构,直至遍历完A树。
public boolean isChild(TreeNode aRoot, TreeNode bRoot){
//A树没有遍历完而且B树不为空
if(aRoot != null && bRoot != null){
//在当前节点检查结构,或者去遍历当前节点的左节点或右节点。
return isQualified(aRoot, bRoot)
|| isChild(aRoot.left, bRoot)
|| isChild(aRoot.right, bRoot);
}
//A树遍历完了或者B树是个null
return false;
}
private boolean isQualified(TreeNode aRoot, TreeNode bRoot){
//检查到了B的末尾
if(bRoot == null)return true;
//如果在检查完B之前A到了底
if(aRoot == null)return false;
//都不是null且val相等,继续检查
if(aRoot.val == bRoot.val){
return isQualified(aRoot.left, bRoot.left)
&& isQualified(aRoot.right, bRoot.right);
}
//都不是null但是val不等
return false;
}我的方法:开始检查一下B是否为空,空的话返回false(定义)。aRoot和bRoot是两个根节点,且都可能在自己的根节点和子节点之间转换。开头,如果aRoot.val和bRoot.val相等了,那么继续对各自的子节点进行对比;如果不相等则移动aRoot并和bRoot进行比较。
public boolean isChildTree(TreeNode aRoot, TreeNode bRoot){
//定义,如果B树为空,返回false
if(bRoot == null){
return false;
}
return isChild2(aRoot, bRoot);
}
private boolean isChild2(TreeNode aRoot, TreeNode bRoot){
//如果检查到了B的末尾
if(bRoot == null)return true;
//A到了末尾但是B没有
if(aRoot == null)return false;
//如果当前节点相同,进行进一步对比;
//对比的结果可能是false,此时继续向下检查aRoot.left与bRoot、aRoot.right与bRoot
if(aRoot.val == bRoot.val){
return (isChild2(aRoot.left, bRoot.left) && isChild2(aRoot.right, bRoot.right))
|| isChild2(aRoot.left, bRoot)
|| isChild2(aRoot.right, bRoot);
}else{//如果当前节点不同,继续向下检查aRoot.left与bRoot、aRoot.right与bRoot
return isChild2(aRoot.left, bRoot)
|| isChild2(aRoot.right, bRoot);
}
}题目19:二叉树的镜像。
书中方法:这道题目可能拿到手没有思路,我们可以在纸上画出简单的二叉树来找到规律。最后我们发现,镜像的实质是对于二叉树的所有节点,交换其左右子节点。搞清楚获得镜像的方法,这道题实际上就变成了一道二叉树遍历的变形。这里选择前序遍历二叉树。
public void change(TreeNode root){
if(root == null)return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
change(root.left);
change(root.right);
}如果改成循环实现:实际上也就是把递归改成循环前序遍历二叉树。注意null是可以被压入栈(队列、HashMap等)的,栈全部是null元素和栈为空不同。
public void change2(TreeNode root){
if(root == null)return;
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode now = stack.pop();
if(now.right != null)stack.push(now.right);
if(now.left != null)stack.push(now.left);
TreeNode temp = now.left;
now.left = now.right;
now.right = temp;
}
}题目20:顺时针打印矩阵。
我的方法:遇到这种题最好在纸上画一画打印路线。我利用了4个标志left、top、right、bottom,表示当前需要打印的左界、上届、右界和下界,换句话说这些界线之外的已经打印了,如此一来判断结束的标准也很清晰,top>bottom或者left>right就表示已经没有需要打印的空间。和第14题中确定结束条件类似,明确下标的含义,就能很快判断结束条件。
public void myPrint(int[][] a){
if(a == null || a.length == 0 || (a.length == 1 && a[0].length == 0)){
return;
}
int left = 0;
int right = a[0].length -1;
int top = 0;
int bottom = a.length - 1;
//用于改变方向,分别代表 从左向右打印,从上往下打印,从又往左打印,从下往上打印。
int[] orientations = new int[]{0, 1, 2, 3};
int count = 0;
while(left <= right && top <= bottom){
//按顺序改变方向
switch (orientations[count%orientations.length]) {
case 0:
//从左向右打印一行,打印完后上界下移
for(int i=left; i<=right; i++){
System.out.print(a[top][i]);
}
top++;
count++;
break;
case 1:
//从上到下打印一列,打印完后右界左移
for(int i=top; i<=bottom; i++){
System.out.print(a[i][right]);
}
right--;
count++;
break;
case 2:
//从右到左打印一行,打印完后下界上移
for(int i=right; i>=left; i--){
System.out.print(a[bottom][i]);
}
bottom--;
count++;
break;
case 3:
//从下到上打印一列,打印完后左界右移
for(int i=bottom; i>=top; i--){
System.out.print(a[i][left]);
}
left++;
count++;
break;
default:
break;
}
}
}书中方法:书中的思路是一圈一圈向内打印,既然这样就要找到每一圈的起点和打印的圈。假设我们用count表示已经打印的圈数。起点就是(count, count);打印的圈是一个矩形条框,矩形可以由一条对角线确定,我们找到矩形条框右下角的点,就能由这个点和起点确定一个需要打印的圈(矩形框)。只要有起点的空间,就继续打印。
public void myPrint2(int[][] a){
if(a == null || a.length == 0 || (a.length == 1 && a[0].length == 0)){
return;
}
int count = 0;
//打印了count次后,如果还留有下一次打印的起始点的位置,继续打印
while(a.length - 2*count > 0 && a[0].length - 2*count > 0){
printRound(a, count);
count++;
}
}打印的范围确定了,剩下就是按顺时针打印每一个圈。
private void printRound(int[][] a, int count){
int endX = a[0].length - 1 - count;
int endY = a.length - 1 - count;
int start = count;
//无论如何第一行都需要打印
for(int i=start; i<=endX; i++){
System.out.print(a[start][i]);
}
//如果不止一行
if(start < endY){
for(int i=start+1; i<=endY; i++){
System.out.print(a[i][endX]);
}
}
if(start < endX && start < endY){
for(int i=endX-1; i>=start; i--){
System.out.print(a[endY][i]);
}
}
if(start < endX && start < endY-1){
for(int i=endY-1; i>start; i--){
System.out.print(a[i][start]);
}
}
}
















