剑指Offer(1-9Java语言描述)

很快秋招就来了,今天开始刷剑指,应该不会太慢吧,之前刷了一百道leetcode,按照tag刷的,想着这个月前把剑指刷一遍,第一波,记录下:

顺便附带亮剑经典语录 一:
碰到我们独立团,就是碰到了一群野狼,在咱们眼里,任何对手,都是我们嘴里的一块肉。

不说鸡汤了,上题

1. 二进制中 1 的个数

题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题思路:
这道题之前leetcode也有看到,也做过。
第一种可以直接用api Integer.bitCount(n)

public class Solution {
    public int NumberOf1(int n) {
        return Integer.bitCount(n);
    }
}

第二种可以用 & 运算的性质,n & (n - 1) 去除n的二进制最低位。

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while (n != 0) {
            n = n & (n - 1);
            count++;
        }
        return count;
    }
}
2. 数值的整数次方

题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
解题思路:
自己直接的遍历循环解法,时间复杂度O(n)


public class Solution {
    public double Power(double base, int exponent) {
        if (base == 0.0) return 0;
        if (exponent == 0) return 1;
        double temp = base;
        for (int i = 1; i < Math.abs(exponent); i++) {
            base *= temp;
        }
        return exponent > 0 ? base : 1 / base;
  }
}

大神的解法思路:
有这样的规律。。。

当n为偶数,a^n =(a^n/2)*(a^n/2)
当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a

因此 (x*x)n/2 可以通过递归求解,并且每次递归 n 都减小一半,因此整个算法的时间复杂度为 O(logN)。

public class Solution {
    public double Power(double base, int exponent) {
        if (base == 0) return 0;
        if (exponent == 0) return 1;
        if (exponent == 1) return base;
        
        double res = Power(base * base, Math.abs(exponent / 2));
        if (exponent % 2 != 0) {
            res *= base;
        }
        return exponent > 0 ? res : 1 / res;
  }
}
3.重复的数字

题目描述:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
解题思路:
本题可以用排序,然后判断即可,不过如果要去时间复杂度O(n),空间复杂度O(n)的话,就不能用排序了,因此注意题目特点,数组元素在 [0, n-1] 范围内,可以将值为 i 的元素调整(交换)到第 i 个位置上,然后遍历的时候判断对应位置是否已经存在值,进行求解。

public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if (length == 0) return false;
        for (int i = 0; i < length; i++) {
            if (numbers[i] == i) continue;
            else{    
                if (numbers[i] == numbers[numbers[i]] ) {
                    duplication[0] = numbers[i];
                    return true;
                }
                swap(numbers,i,numbers[i]);
            }
        }
        return false;
        
    }
    private void swap(int [] arr,int a ,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}
4.二维数组中的查找

题目描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
这道题刚开始考虑二分查找,结果发现不行,后来看数组的特点,从左到右递增,从上到下递减,因此右下角的数字肯定最大,左上角的数字肯定最小,可以考虑从右上角或者左下角判断与target值 得大小,然后进行移动。
这里我从右上角开始判断。

public class Solution {
    public boolean Find(int target, int [][] array) {
        if (array.length == 0) return false;
        int row = array.length;
        int column = array[0].length;
        int r = 0, c = column - 1; 
        while (r < row && c >= 0) {
            if (target == array[r][c]) return true;
            else if (target > array[r][c]) r++;
            else c--;
        }
        return false;
    }
}
5.替换空格为指定字符

题目描述:
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解题思路:

  1. 其实可以使用Java一行代码即可实现,用replace 方法。。。皮了。

  2. 也可以用StringBuffer中的append来实现,这样就需要再创建一个Strinbuffer对象。

  3. 这里还是老老实实从后往前遍历,首先先定义两个长度,temp1 和 temp2 ,当遍历到一个空格时,在尾部填充两个任意字符。

public class Solution {
   public String replaceSpace(StringBuffer str) {
       int temp1 = str.length() - 1;
       for (int i = 0; i <= temp1; i++) {
           if (str.charAt(i) == ' '){
               str.append("  ");	//有多少空格就加多少 "  "
           }
       }
       int temp2 = str.length() - 1;
       while (temp1 >=0 && temp2 > temp1) {
           char c = str.charAt(temp1--);
           if (c == ' '){
               str.setCharAt(temp2--,'0');
               str.setCharAt(temp2--,'2');
               str.setCharAt(temp2--,'%');
           } else {
               str.setCharAt(temp2--,c);
           }
       }
       return str.toString();
       
   }
}
6.从尾到头打印链表

题目描述:
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
Input : 1 -> 2 -> 3
Output : 3 -> 2 -> 1

解题思路:
题目刚看,感觉就是链表反转,那么如何进行链表反转呢,其实可以使用 头插法,这里 head 就是头,head不存储数据,head.next存储反转后的链表。反转后再遍历就是了。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ListNode head = new ListNode(-1);
        while (listNode != null) {
            ListNode temp = listNode.next;
            listNode.next = head.next;
            head.next = listNode;
            listNode = temp;
        }
        ArrayList<Integer> list = new ArrayList();
        head = head.next;
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        return list;
    }
}

本题也可以说用栈来实现,利用栈的特点,后进先出。

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
       Stack<Integer> stack = new Stack();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        ArrayList<Integer> list = new ArrayList();
        while (!stack.isEmpty() ) {
            list.add(stack.pop());
        }
        return list;
    }
}
7.重建二叉树

题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

解题思路:注意二叉树性质,如果涉及到二叉树,应当考虑使用递归来解决,首先根据前序遍历第一个值就是根节点,然后根据根结点在中序序列中的位置分割出左右两个子序列,最后递归地对左子树和右子树分别递归使用同样的方法继续分解。
这里注意Arrays.copyOfRange()的用法,(左闭右开)附上API:
在这里插入图片描述


import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if (pre.length == 0 || in.length == 0) return null;
        int value = pre[0];
        TreeNode root = new TreeNode(value);
        for (int i = 0; i < in.length; i++){
            if (in[i] == value) {
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
                root.right =reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,in.length),Arrays.copyOfRange(in,i+1,in.length));
                break;
            }
        }
        return root;
    }
}
8.二叉树的下一个结点

题目描述:
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路:由于每个节点都有指向父节点的指针,因此先求的根节点,然后再利用根节点和用链表存储中序遍历的值,然后判断返回链表下一个即可,需注意是否是最后一个。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;
 
    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    static List<TreeLinkNode> list = new ArrayList();
    public TreeLinkNode GetNext(TreeLinkNode pNode){
        if (pNode == null) return null;
        TreeLinkNode p = pNode;
        while (pNode.next != null) {
            pNode = pNode.next;
        }
        getMidTravel(pNode);
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) == p) {
                return (i == list.size() - 1) ? null : list.get(i+1);
            }
        }
        return null;
    }
    //中序遍历
    private static void getMidTravel(TreeLinkNode node) {
        if (node != null) {
            getMidTravel(node.left);
            list.add(node);
            getMidTravel(node.right);
        }
         
    }
}

上述方法时间复杂度O(N),空间复杂度:O(n),下面这种可以做到空间复杂度O(n)
可以把中序下一结点归为几种类型:

  • 有右子树,下一结点是右子树中的最左结点

  • 无右子树,且结点是该结点父结点的左子树,则下一结点是该结点的父结点

  • 无右子树,且结点是该结点父结点的右子树,则我们一直沿着父结点追朔,直到找到某个结点是其父结点的左子树,如果存在这样的结点,那么这个结点的父结点就是我们要找的下一结点。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode){
        if (pNode == null) return null;
        if (pNode.right != null) {
            TreeLinkNode temp = pNode.right;
            if (temp.left != null) {
                return temp.left;
            }
            return temp;
        }else {
            while (pNode.next != null) {
                TreeLinkNode temp = pNode.next;
                if (temp.left == pNode) return temp;
                pNode = pNode.next;
            }
        }
        return null;
        
    }
}
9. 用两个栈实现队列

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

解题思路:之前在LeetCode刷过类似题目,当时还写了一篇文章LeetCode中的栈和队列的问题,这里使用两个栈,利用栈后进先出特点和队列先进先出特点进行解决即可。需要注意的就是当第二个栈stack2为空时才往里面push。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
	    
    }   
    public int pop() {
    //当第二个栈stack2为空时才往里面push。
        while (stack2.empty()){
            while (!stack1.empty()){
		        stack2.push(stack1.pop());
	        }
        }     
        return stack2.pop();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值