题目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时终止循环),我们就可以知道确切的奇偶分界,这一点也可以利用到快速排序中。

java代码e的x次方 java表示e的x次方_数据结构

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]);
            }
        }
    }