剑指offer学习笔记(Java实现)(11-20)
来源:互联网 发布:杭州淘宝化妆师招聘 编辑:程序博客网 时间:2024/05/17 23:19
题目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]); } } }
- 剑指offer学习笔记(Java实现)(11-20)
- 剑指offer学习笔记(Java实现)(1-10)
- 剑指offer学习笔记(Java实现)(21-25)
- 剑指offer学习笔记(Java实现)(26-30)
- 剑指offer学习笔记(Java实现)(31-40)
- 《剑指offer》笔记(java)
- (剑指offer)JAVA实现
- 剑指offer(Java实现)
- 《剑指offer》 学习笔记(一)
- 剑指offer学习笔记(一)
- 剑指Offer学习笔记(2)
- 剑指Offer学习笔记(一)
- 剑指Offer笔记<JAVA版>(一)
- 剑指Offer笔记<JAVA版>(二)
- 剑指Offer笔记<JAVA版>(三)
- JAVA实现替换空格(《剑指offer》)
- 剑指offer习题JAVA实现(一)
- 剑指offer习题JAVA实现(二)
- LitePal无法使用自定义主键的临时解决方案
- Android从零开搞系列:自定义View(3)Canvas基本API+综合应用+开源分析
- 如何查看ubuntu下显卡驱动是否已经成功安装
- Spark性能调优
- Shiro身份验证Test
- 剑指offer学习笔记(Java实现)(11-20)
- 使用Socket实现远程通信
- NetRiver - IPv4协议转发实验
- 16 - 12 - 13 B树(2-3 树,2-3-4 树)
- python 调用 Stanford NLP 的问题
- python中__name__=='__main__'的实例研究
- Codechef DECEMBER16 BOUNCE
- 图像处理与计算机视觉基础,经典以及最近发展
- 2016-12-12