牛客剑指offer:题解(11-20)

欢迎指正

题解(01-10):link

题解(11-20):link

题解(21-30):link

题解(31-40):link

题解(41-50):link

题解(51-60): link

题解(61-67): link


11.二进制中1的个数

题目描述: 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

1.解法一:循环右移

  1. >> :表示右移,如果该数为正,则高位补0,如果为负数,则高位补1。此时的右移就是除2操作
  2. >>>:表示无符号右移,也叫逻辑右移。如果该数为正,则高位补0,如果该数为负数,则右移后高位同样补0。此时的负数右移就不是单纯的除2操作了
public class Solution {
    public int NumberOf1(int n) {
        int res = 0;
        // int 一共32位,每一位看看是不是1,然后向右移动32次
        for (int i = 0;i < 32;i ++) {
            if ((n & 1) == 1)
                res ++;
            n = n >>> 1;
        }
        return res;
    }
}

2.解法二:巧用n & (n - 1)消去最后的一位1

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while (n != 0) {
            count ++;
            // 利用 n & (n - 1)可以消去n最后的一位1
            n = n & (n - 1);
        }
        return count;
    }
}

12.数值的整数次方 (leetcode : pow(x,n))

题目描述: 给定一个double类型的浮点数baseint类型的整数exponent。求baseexponent次方。

保证baseexponent不同时为0

1.解法一:分治,快速幂乘法

public class Solution {
    // leetcode 上 pow(x, n)
    public double Power(double base, int exponent) {
        // 避免出现 exponent = Integer.MIN_VALUE 这个值带来的问题
        long N = exponent;    
        // 先将指数化为正的,基数化为倒数。结果不变
        if (exponent < 0) {
            // 这个地方N取-N非常容易犯错
            // 如果N = -n,那么-n会导致整型溢出,最终结果还是-n
            // 赋值的过程有三步
            //1.先取得n的值
            //2.将n进行取相反数,这个时候就会溢出了
            //3.将取反之后的值赋给N,前一步已经出现问题了
            N = -N;
            base = 1 / base;
        }
        return fastPow(base, N);
    }
    private double fastPow(double x, long N) {
        if (N == 0) return 1;
        if (N == 1) return x;
        // 先计算一半,根据上面的公式返回结果值
        double half = fastPow(x, N / 2);
        // 如果指数为奇数返回什么,如果是偶数又返回什么。
        return N % 2 == 1 ? half * half * x : half * half;
    }
}

2.解法二:循环的快速幂乘法

public class Solution {
    // leetcode 上 pow(x, n)
    public double Power(double base, int exponent) {
        // 避免出现 exponent = Integer.MIN_VALUE 这个值带来的问题
        long N = exponent; 
        if (exponent < 0) {
            N = -N;
            base = 1 / base;
        }
        double res = 1.0, curr = base;
        for (long i = N;i > 0;i /= 2) {
            if (i % 2 == 1) res = res * curr;
            curr = curr * curr;
        }
        return res;
    }
}

13.调整数组顺序使奇数位于偶数前面

题目描述: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

1.解法一:暴力法,两次循环取得奇数和偶数

public class Solution {
    public void reOrderArray(int [] array) {
        int[] res = new int[array.length];
        int front = 0, tail = array.length - 1;
        // 获得奇数放到辅助数组前面
        for (int i = 0;i < array.length;i ++) 
            if (array[i] % 2 == 1) 
                res[front ++] = array[i];
        // 取得偶数放到辅助数组后面
        for (int j = array.length - 1;j >= 0;j --) 
            if (array[j] % 2 == 0) 
                res[tail --] = array[j];
        for (int k = 0;k < res.length;k ++) 
            array[k] = res[k];
    }      
}

2.解法二:原地操作,有点类似于冒泡

public class Solution {
    public void reOrderArray(int [] array) {
        if (array == null || array.length == 0) return;
        for (int i = 0;i < array.length;i ++) {
            for (int j = 0;j < array.length - 1 - i;j ++) {
                // 依次把偶数沉到数组末尾去
                while (array[j] % 2 == 0 && array[j + 1] % 2 == 1) {
                    int tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                }
            }
        }
    }
}

14.链表中倒数第k个节点

题目描述: 输入一个链表,输出该链表中倒数第k个结点。

1.解法一:哈希表

  1. 将每一个节点的索引位置,与这个节点的映射关系存入一个 Map
  2. 构建了索引后,就能够通过索引与 k 之间的数学关系取得倒数第 k 个元素
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        Map<Integer, ListNode> map = new HashMap<>();
        int index = 0;
        while (head != null) {
            map.put(index ++, head);
            head = head.next;
        }
        // 如果index = 0,说明 head 为空
        if (index == 0 || k > size) return null;
        // 第index - k 个元素就是倒数第k个元素
        return map.get(index - k);
    }
}

2.解法二:双指针

  1. 快指针先往后走k步,要是走不到,说明链表长度不够k,返回null即可
  2. 走到k步之后,慢指针和快指针一起往后走,当快指针走到最后一个节点,慢指针指向倒数第k个,快慢之间差一个 k 步
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if (head == null || k < 0) return null;
        ListNode slow = head, fast = head;
	    // 快指针先走k步
        for (int i = 0;i < k;i ++) {
            // 时刻判断快指针是否超出链表长度
            if (fast == null) return null;
            fast = fast.next;
        }       
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

15.反转链表(背住背住)

题目描述: 输入一个链表,反转链表后,输出新链表的表头。

1.解法一:递归

  1. 将整个链表看成两个部分,为 head -> ReverseList(head.next) ,然后我们要做的工作就转换为反转这仅有的两个节点
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode next = ReverseList(head.next);
        head.next.next = head;
        head.next = null;
        return next;
    }
}

2.解法二:迭代

public class Solution {
    public ListNode ReverseList(ListNode head) {
        // 使用迭代
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        // 因为循环跳出的判断就是 curr == null
        return prev;
    }
}

16.合并两个排序的链表

题目描述: 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

1.解法一:递归

public class Solution {
    // 使用递归。
    public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null) {
            return list2;
        } else if (list2 == null) {
            return list1;
        } else if (list1.val <= list2.val) {
            list1.next = Merge(list1.next, list2);
            return list1;
        } else {
            list2.next = Merge(list1, list2.next);
            return list2;
        }
    }
}

2.解法二:迭代,使用双指针进行归并

public class Solution {
    // 使用双指针,归并
    public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode dummy = new ListNode(-1);
        ListNode prev = dummy;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                prev.next = list1;
                list1 = list1.next;
            } else {
                prev.next = list2;
                list2 = list2.next;
            }
            prev = prev.next;
        }
        // 出来说明至少有一个链表为空
        // l1 为空接上l2,l2为空接上l1
        prev.next = list1 == null ? list2 : list1; 
        return dummy.next;
    }
}

17.树的子结构

题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

1.解法一:递归

要查找树 A 中是否存在和树 B 结构一样的子树,我们可以分为两步:

  1. 第一步,在树 A 中找到和树 B 的根结点值一样的结点 R;
  2. 第二步,判断树 A 中以 R 为根结点的子树是不是包含和树 B一样的结构。
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if (root1 == null || root2 == null) return false;
        boolean res = false;
        if (root1.val == root2.val) { // 有相同的根节点,再进行第二步
            res = doesTree1HasTree2(root1, root2);
        }
        if (!res) // 不满足,在左子树中继续查找
            res = HasSubtree(root1.left, root2);
        if (!res) // 不满足,在右子树中继续查找
            res = HasSubtree(root1.right, root2);
        return res;
    }
    // 判断树A中以R为根结点的子树是不是包含和树B一样的结构。
    private boolean doesTree1HasTree2(TreeNode r1, TreeNode r2) {   
        // 对于r2的判断要在r1之前,r2不为空r1为空返回false
        // r2为空返回true
        if (r2 == null) return true;
        if (r1 == null) return false;
        if (r1.val != r2.val) return false;
        return doesTree1HasTree2(r1.left, r2.left) && doesTree1HasTree2(r1.right, r2.right);
    }
}
// 简洁版本,思路相同
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null) return false;
        // 只要在任何地方能够找到满足条件的子结构就行
        return dfs(A, B) || isSubStructure(A.left, B)
            || isSubStructure(A.right, B);
    }

    private boolean dfs(TreeNode A, TreeNode B) {
        if (B == null) return true;
        if (A == null) return false;
        return A.val == B.val && dfs(A.left, B.left) && dfs(A.right, B.right);
    }
}

18.二叉树的镜像

描述见题目链接

1.解法一:递归

求一棵树的镜像的过程:先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子结点。当交换完所有的非叶结点的左、右子结点后,就可以得到该树的镜像。

public class Solution {
    public void Mirror(TreeNode root) {
        if (root != null) {
            // 只要有一个孩子节点不为空就可以进行交换
            if (root.left != null || root.right != null) {
                // 交换左右孩子节点
                TreeNode tmp = root.left;
                root.left = root.right;
                root.right = tmp;
                // 以左孩子节点为根节点再做交换
                Mirror(root.left);
                // 以右孩子节点为根节点再做交换
                Mirror(root.right);
            }
        }
    }
}

19.顺时针打印矩阵

描述见题目链接

1.解法一:用上下左右来标记方向

  1. 根据题意我们可以知道,顺时针打印一直在循环四个方向: 向右->向下->向左->向上。 所以我们要做的就是在他们循环的时候控制循环的边界,于是使用四个变量(上下左右)来控制。需要注意特殊情况(一行或一列)的边界条件
  2. 不需要额外的空间开销
  3. 简单来说,就是不断地收缩矩阵的边界
    定义四个变量代表范围,up、down、left、right
    1. 向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
    2. 向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
    3. 向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
    4. 向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错
public class Solution {
    ArrayList<Integer> res;
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        res = new ArrayList<>();
        if (matrix == null) return null;
        // n : row ,m : col
        int n = matrix.length, m = matrix[0].length;
        int left = 0, right = m - 1;
        int up = 0, down = n - 1;
        while (left <= right && up <= down) {
            // 向右走
            for (int j = left;j <= right;j ++)
                res.add(matrix[up][j]);
            // 向下走
            for (int i = up + 1;i <= down;i ++)
                res.add(matrix[i][right]);
            // 避免只有一行的情况出现:只有一行就不从右往左了,避免重复
            if (up < down) {
                for (int j = right - 1;j >= left;j --)
                    res.add(matrix[down][j]);
            }
            // 避免只有一列的情况出现:只有一列就不从下往上了,避免重复
            if (left < right) {
                for (int i = down - 1;i > up;i --)
                    res.add(matrix[i][left]);
            }           
            left ++; right --; up ++; down --;
        }
        return res;
    }
}

20.包含min函数的栈

题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min函数(时间复杂度应为O(1) )。

注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

1.解法一:一个栈存数据,另一个栈存最小值

public class Solution {
    Stack<Integer> stack1 = new Stack<>();	// 存数据
    Stack<Integer> stack2 = new Stack<>();  // 存最小值
    public void push(int node) {
        stack1.push(node);
        // 如果stack2为空,此时的最小值就是刚push进来的值
        if (stack2.isEmpty()) 
            stack2.push(node);
        // 否则,要是刚进来的这个值小于min(),则将刚进来的值push进stack2
        else if (node <= this.min()) 
            stack2.push(node);
    }   
    public void pop() {
        // 避免栈空
        if (stack1.isEmpty())	throw new RuntimeException("栈为空");      
        int top = stack1.peek();
        // 如果出栈的是当前最小值
        if (top == this.min())
            stack2.pop();
        stack1.pop();
    }
    public int top() {
        if (stack1.isEmpty())	throw new RuntimeException("栈为空");
        return stack1.peek();
    }  
    public int min() {
        if (stack2.isEmpty())	throw new RuntimeException("栈为空");
        return stack2.peek();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值