【剑指offer第二版】JAVA刷题总结-ch2

3.数组中重复的数字

3-1 找出数组中重复的数字

思路:交换下标,时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:

class Solution {
    public int duplicateInArray(int[] nums) {
        for(int i =0; i<nums.length;i++){
            if(nums[i]<0 || nums[i]>nums.length-1)
                return -1;
        }
        int dumplication=0;
        for(int i=0; i< nums.length; i++){
            while(nums[i]!=i){
                if(nums[nums[i]]==nums[i]){
                    dumplication = nums[i];
                    return dumplication;
                }
                else{
                    int tmp = nums[i];
                    nums[i] = nums[tmp];
                    nums[tmp] = tmp;
                }
            }
        }
        return -1;
    }
}

3-2 不修改数组找出重复的数字

思路:抽屉原理+分治法+区间不相交;按数字划分1-n;时间复杂度O(nlogn);空间复杂度O(1);不保证找出所有重复数字。

class Solution {
    public int duplicateInArray(int[] nums) {
        int begin = 1;
        int end = nums.length-1;
        while(end >= begin){
            int mid = (end+begin)>>1;//除以二
            int count = countRange(nums,begin,mid);
            if(end == begin){
                if(count>1) return begin;
                else return -1;
            }
            if(count>(mid-begin+1)){
                end = mid;
            }
            else{
                begin = mid+1;
            }
        }
        return -1;
    }
    public static int countRange(int[] nums, int begin, int end){
        int count=0;
        for(int i = 0; i< nums.length; i++){
            if(nums[i]>=begin && nums[i]<=end){
                count++;
            }
        }
        return count;
    }
}

4.二维数组中的查找

思路:发现每个子矩阵右上角的数的性质:x左边的数都小于等于x,x下边的数都大于等于x。如果 x等于target,则说明我们找到了目标值,返回true;如果 x 小于target,则 x左边的数一定都小于target,我们可以直接排除当前一整行的数;如果 x大于target,则 xx 下边的数一定都大于target,我们可以直接排序当前一整列的数。时间复杂度O(m+n)。

class Solution {
    public boolean searchArray(int[][] array, int target) {
        int newrow = 0;
        int newcol = array.length-1;
        int i = 0;
        while(newrow < array.length && newcol>=0){
            if(array[newrow][newcol] == target){
                return true;
            }
            else if(array[newrow][newcol] > target){
                newcol = newcol-1;
            }
            else{
                newrow = newrow+1;
            }
        }
        return false;
    }
}

5. 替换空格

思路:双指针。先把stringbuffer扩展(append),在用两个分别指向两个stringbuffer尾部的指针向前移动。时间复杂度O(n)。

class Solution {
    public String replaceSpaces(StringBuffer str) {
        int P1 = str.length()-1;
        for(int i = 0; i < P1+1; i++){
            if(str.charAt(i)==' '){
                str.append("  ");
            }
        }
        int P2 = str.length()-1;
        while(P1!=P2 && P1>=0 && P2>=0){
            if(str.charAt(P1)!=' '){
                str.setCharAt(P2,str.charAt(P1));
                P2--;
                P1--;
            }
            else{
                P1--;
                str.setCharAt(P2, '0');
                str.setCharAt(P2-1, '2');
                str.setCharAt(P2-2, '%');
                P2 = P2-3;
            }
        }
        return str.toString();
    }
}

6.从尾到头打印链表

思路:使用栈存储,后进先出,(基于递归的话,函数调用栈很长,可能导致溢出),用栈实现鲁棒性会更好。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] printListReversingly(ListNode head) {
        Stack<Integer> stack = new Stack<>();
        int i = 0;
        while(head!=null){
            stack.push(head.val);
            head = head.next;
            i++;
        }
        int[] result = new int[i];
        int j=0;
        while(!stack.empty()){
            result[j] = stack.pop();
            j++;
        }
        return result;
    }
}

7. 重建二叉树

思路:递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点。时间复杂度O(n)。

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public Map<Integer, Integer> findIndex = new HashMap<>();

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for(int i=0; i<inorder.length; i++){
            findIndex.put(inorder[i], i);
        }
        return reconstruct(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1);
        
    }
    public TreeNode reconstruct(int[] preorder, int[] inorder, int pl, int pr, int il, int ir){
        if(pl>pr) return null;
        int k = findIndex.get(preorder[pl]);
        TreeNode root = new TreeNode(preorder[pl]);
        TreeNode left = reconstruct(preorder, inorder, pl+1, pl+k-il, il, k-1);
        TreeNode right = reconstruct(preorder, inorder, pl+k-il+1, pr,k+1, ir);
        root.left = left;
        root.right = right;
        return root;
        
    }
}

8. 二叉树的下一个节点

思路:时间复杂度O(h)。这道题目就是让我们求二叉树中给定节点的后继。分情况讨论即可。如果当前节点有右儿子,则右子树中最左侧的节点就是当前节点的后继。比如F的后继是H;如果当前节点没有右儿子,则需要沿着father域一直向上找,找到第一个是其father左儿子的节点,该节点的father就是当前节点的后继。比如当前节点是D,则第一个满足是其father左儿子的节点是C,则C的father就是D的后继。(8的后继是3)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode father;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode inorderSuccessor(TreeNode p) {
        if(p.right != null){
            p = p.right;
            while(p.left != null){
                p = p.left;
            }
            return p;
        }
        else if(p.father != null){
            if(p == p.father.left){
                return p.father;
            }
            else{
                while(p.father != null){
                    if(p.father == p.father.left) return p.father;
                    p = p.father;
                }
            }
        }
        return null;
    }
}

9. 用两个栈实现队列

class MyQueue {

    /** Initialize your data structure here. */
    public Stack<Integer> stack1 = new Stack<Integer>();
    public Stack<Integer> stack2 = new Stack<Integer>();
    public MyQueue() {

    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {
        while(!stack1.empty()){
            stack2.push(stack1.peek());
            stack1.pop();
        }
        stack1.push(x);
        while(!stack2.empty()){
            stack1.push(stack2.peek());
            stack2.pop();
        }
    }
    
    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        int num = stack1.peek();
        stack1.pop();
        return num;
    }
    
    /** Get the front element. */
    public int peek() {
        return stack1.peek();
    }
    
    /** Returns whether the queue is empty. */
    public boolean empty() {
        return stack1.empty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

10.斐波那契数列

10-1 求斐波那契数列的第n项

思路:循环,记录前一个和前两个数。 不用递归。递归是函数调用自身,调用存在时间和空间小号,每调用一次,都需要在内存占中分配空间保存参数、返回地址、和临时变量,而且入栈和出栈都需要时间,效率不如循环。递归也可能引起调用栈溢出。时间复杂度O(n)

class Solution {
    public int Fibonacci(int n) {
        if(n==0) return 0;
        if(n==1) return 1;
        int fnminus1 = 1;
        int fnminus2 = 0;
        int fn = 0;
        for(int i=2; i<=n; i++){
            fn = fnminus1 + fnminus2;
            fnminus2 = fnminus1;
            fnminus1 = fn;
        }
        return fn;
    }
}

10-2 青蛙跳台阶&&矩形覆盖

思路:解法和斐波那契数列类似。

10-3 变态跳台阶

思路(1)动态规划:

(2)数学推导:

public int JumpFloorII(int target) {//动态规划
    int[] dp = new int[target];
    Arrays.fill(dp, 1);
    for (int i = 1; i < target; i++)
        for (int j = 0; j < i; j++)
            dp[i] += dp[j];
    return dp[target - 1];
}
public int JumpFloorII(int target) {//数学推导
    return (int) Math.pow(2, target - 1);
}

11.旋转数组的最小数字

思路:画图,注意特殊情况(1)12345 (2)3412333(3)1111123

class Solution {
    public int findMin(int[] nums) {
        if(nums.length == 0) return -1;
        int size = nums.length - 1;
        if(nums[size] > nums[0]) return nums[0];//情况1
        while(size > 0 && nums[size] == nums[0]) size--;//情况2
        int left = 0;
        int right = size;
        while(left < right){
            int mid = right + left >> 1;
            if(nums[mid] >= nums[0]) left = mid + 1; //情况3
            else right = mid;
        }
        return nums[right];
    }
}

12.矩阵中的路径

思路:回溯法,dfs,dfs用递归、栈来解决。

class Solution {
    public boolean hasPath(char[][] matrix, String str) {
        for(int i=0; i<matrix.length; i++){
            for(int j=0; j<matrix[0].length; j++){
                if(dfs(matrix, str, 0, i, j) == true)
                    return true;
            }
        }
        return false;
    }
    
    public boolean dfs(char[][] matrix, String str, int index, int i, int j){
        if(matrix[i][j] != str.charAt(index)) return false;
        if(index == str.length()-1) return true;
        int[] xRange={0, 0, -1, 1}, yRange={1, -1, 0, 0};
        char ch = matrix[i][j];
        matrix[i][j]='*';//访问过
        for(int range=0; range<4; range++){
            if(i+xRange[range]>=0 && i+xRange[range]<matrix.length && j+yRange[range]>=0 && j+yRange[range]<matrix[0].length){
                if(dfs(matrix, str, index+1, i+xRange[range], j+yRange[range]) == true) return true;
            }
        }
        matrix[i][j]=ch;//还原数组
        return false;
    }
}

13.机器人的运动范围

思路:回溯法,bfs,数据范围比较大,适合用宽搜,没有爆栈的风险。


class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        if(rows==0 || cols==0) return 0;
        Queue<int[]> queue = new LinkedList<int[]>();
        int[] pair = {0, 0};
        queue.add(pair);
        int count = 1;
        boolean[][] visited = new boolean[rows][cols];
        for(int i=0; i<rows; i++){
            for(int j=0; j<cols; j++){
                visited[i][j] = false;
            }
        }
        visited[0][0] = true;
        int[] dx={0, 0, -1, 1}, dy={1, -1, 0, 0};
        while(!queue.isEmpty()){
            int[] nowPair = queue.poll();
            for(int i=0; i<4; i++){
                int x = nowPair[0]+dx[i], y = nowPair[1]+dy[i];
                int[] newPair = {x, y};
                if(x>=0 && x<rows && y>=0 && y<cols && visited[x][y] == false){
                    if(check(newPair, threshold) == true){//这样写比acwing上面的queue更干净
                        queue.add(newPair);
                        visited[x][y] = true;
                        count++;
                    }
                } 
            }
        }
        return count;

    }
    public boolean check(int[] pair, int threshold){//传引用
        int sum = 0;
        int num1 = pair[0], num2 = pair[1];
        while(num1!=0){
            sum += num1%10;
            num1 = num1/10;
        }
        while(num2!=0){
            sum += num2%10;
            num2= num2/10;
        }
        if(sum > threshold) return false;
        else return true;
    }
}

14.剪绳子

思路:(1)动态规划

(2)贪心算法:如果n>=5;那么3(n-3)>n && 2(n-2)>n && 3(n-3)>2(n-2),所以尽可能把绳子剪成长度为3和2的子绳子,3优先。

class Solution {//dp
    public int maxProductAfterCutting(int length)
    {
        if(length<2) return 0;
        if(length==2) return 1;
        if(length==3) return 2;//必须剪一刀
        int[] product = new int[length+1];
        product[1] = 1;
        product[2] = 2;
        product[3] = 3;//不剪收益最高
        for(int i=4; i<=length; i++){
            int max=0;
            for(int j=1; j<=i/2; j++){
                if(product[j]*product[i-j]>max) 
                    max = product[j]*product[i-j];
            }
            product[i] = max;
        }
        return product[length];
        
    }
}
class Solution {//贪心
    public int maxProductAfterCutting(int length)
    {
        if(length==2) return 1;
        if(length==3) return 2;
        if(length==4) return 4;
        int num3 = length/3;
        int result = 1;
        if(length-num3*3 == 1) num3-=1;
        int num2 = (length - num3*3)/2;
        for(int i=0; i<num3; i++){
            result = result*3;
        }
        for(int i=0; i<num2;i++){
            result = result*2;
        }
        return result; 
    }
}

15. 二进制中1的个数

思路:把最右面的1变成0:n&(n-1)

class Solution {
    public int NumberOf1(int n)
    {
        int count = 0;
        while(n!=0){
            n = (n-1)&n;
            count++;
        }
        return count;
    }
}
发布了16 篇原创文章 · 获赞 0 · 访问量 3124

猜你喜欢

转载自blog.csdn.net/Calliope1997/article/details/104196037