剑指Offer/11-20

博客涵盖多个算法题目及解题思路,包括二进制中1的个数、数值的整数次方、调整数组顺序、链表操作、树的子结构、二叉树镜像、矩阵打印和含min函数的栈等,涉及位运算、快速幂、双指针、递归、广度优先搜索等多种算法技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


11. 二进制中1的个数

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

解题思路:n&(n-1)这一操作会消除最低位的1。一直这样操作,并同时更新n的值,当n的值为0的时候,结束循环。

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0){
            count++;
            n = n & (n-1);
        }
        return count;
    }
}

12. 数值的整数次方

题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

解题思路:采用非递归快速幂的方式进行求解。在指数exponent不等于0的情况下执行语句,当 (exponent & 1)==1 的时候,说明该指数的二进制数所对应的数为1,令 result*=base。在循环语句中,每次更新 base 的值,令 base*=base,exponent右移一位。

public class Solution {
     public double Power(double base, int exponent) {
         if(exponent < 0){
             base = 1/base;
             exponent = -exponent;
         }
         double result = 1.0d;
         while(exponent != 0){
             if((exponent&1) == 1){//说明这一位有1,将x乘进结果
                 result *= base;
             }
             base *= base;
             exponent >>= 1;
         }
         return result;
     }
 }

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

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

解题思路:采用 in-place 方法,记录一个变量 slow 表示已经将奇数放好的下一个位置,显然最开始 slow=0 表示还没有一个奇数放好。fast 表示数组的下标,初始值为0,表示从下标0开始遍历。如果遇到偶数,fast++;如果遇到奇数,假设位置为 fast,就将此奇数插入到 slow 所指的位置,然后i往后移动一个位置,在插入之前,显然会涉及到数据的移动,也就是将[slow, fast-1]整体往后移动。直到整个数组遍历完毕。

public class Solution {
    public void reOrderArray(int [] array) {
        int slow = 0;//用于定位第一次出现偶数时所对应得数组下标
        int fast = 0;//用于遍历整个数组
        while(fast < array.length){
            if( array[fast]%2 != 0 ){//奇数
                int temp = array[fast];
                for(int i = fast; i > slow; i--){
                    array[i] = array[i-1];
                }
                array[slow] = temp;
                slow++;
                fast++;
            } else{
                fast++;
            }
        }
    }
}

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

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

解题思路:采用双指针的方法。设置两个指针(slow,fast),先让fast先行k步,然后slow和fast指针同步前进,直到fast走到链表尾部,slow指针所指向就是倒数第k个结点。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0){
            return null;
        }
        ListNode slow = head;
        ListNode fast = head;
        //快指针先走k步
        for(int i = 0; i < k; i++){
            if(fast == null){//如果中途快指针走到头了,说明k大于链表长度(不可忽略)
                return null;
            }
            fast = fast.next;
        }
        while(fast != null){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

15. 反转链表

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

解题思路:头插法

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode pre = null;
        ListNode next = null;
        while(head != null){
            next = head.next;//记录当前结点的下一个结点
            head.next = pre;
            pre = head;
            head = next;//更新当前结点
        }
        return pre;
    }
}

16. 合并两个排序的链表

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

解题思路:
初始化:定义cur指向新链表的头结点
操作:
(1)如果 list1 指向的结点值小于等于 list2 指向的结点值,则将 list1 指向的结点值链接到 cur 的 next 指针,然后 list1 指向下一个结点值;
(2)否则,让 list2 指向下一个结点值
(3)循环步骤(1)和步骤(2),直到 list1 或者 list2 为 null
(4)将 list1 或者 list2 剩下的部分链接到 cur 的后面

技巧:一般创建单链表,都会设一个虚拟头结点,也叫哨兵,因为这样每一个结点都有一个前驱结点。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode merge = new ListNode(-1);
        ListNode cur = merge;
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                cur.next = list1;
                list1 = list1.next;
            }else{
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if(list1 != null){
            cur.next = list1;
        }
        if(list2 != null){
            cur.next = list2;
        }
        return merge.next;
    }
}

17. 树的子结构

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

解题思路:递归
递归函数的功能:判断2个树是否有相同的结构,如果相同,返回true,否则返回false
递归终止条件:
(1)如果树B为空,返回true,此时,不管树A是否为空,都为true
(2)否则,如果树B不为空,但是树A为空,返回false,此时B还没空但A空了,显然false
下一步递归参数:
(1)如果A的根节点和B的根节点不相等,直接返回false
(2)否则,相等,就继续判断A的左子树和B的左子树,A的右子树和B的右子树

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root2 == null || root1 == null){
            return false;
        }
        if(root1.val == root2.val && recur(root1.left, root2.left) && recur(root1.right, root2.right)){
            return true;
        }
        return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }
    private boolean recur(TreeNode root1,TreeNode root2) {
        if(root2 == null){
            return true;
        }
        if(root1 == null){
            return false;
        }
        if(root1.val == root2.val){
            return recur(root1.left, root2.left) && recur(root1.right, root2.right);
        }else{
            return false;
        }
    }
}

18. 二叉树的镜像

题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
在这里插入图片描述
解题思路:
镜像就是将“根”节点的左右两个“子”节点互换,类似于数组的元素交换(运用临时节点temp),利用二叉树的广度优先搜索即可

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.*;
public class Solution {
    public void Mirror(TreeNode root) {
//         //递归版本
//         if(root == null){
//             return;
//         }
//         TreeNode temp = root.left;
//         root.left = root.right;
//         root.right = temp;
//         Mirror(root.left);
//         Mirror(root.right);
        
        //非递归版本——利用二叉树的广度优先搜索  层次遍历
        if(root == null){
            return;
        }
        TreeNode temp, cur;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            int len = queue.size();
            for(int i = 0; i < len; i++){
                cur = queue.poll();
                //先交换左右子树 再入队
                temp = cur.left;
                cur.left = cur.right;
                cur.right = temp;
                if(cur.left != null){
                    queue.offer(cur.left);
                }
                if(cur.right != null){
                    queue.offer(cur.right);
                }
            }
        }
    }
}

19. 顺时针打印矩阵

题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

解题思路:
不断地收缩矩阵的边界,定义四个变量代表范围,up、down、left、right
(1)向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
(2)向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
(3)向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
(4)向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错

public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return list;
        }
        int up = 0;
        int down = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;
        while(true){
            //最上面一行
            for(int col = left; col <= right; col++){
                list.add(matrix[up][col]);
            }
            //向下逼近
            up++;
            //判断是否越界
            if(up > down){
                break;
            }
            //最右边一行
            for(int row = up; row <= down; row++){
                list.add(matrix[row][right]);
            }
            //向左逼近
            right--;
            //判断是否越界
            if(left > right){
                break;
            }
            //最下面一行
            for(int col = right; col >= left; col--){
                list.add(matrix[down][col]);
            }
            //向上逼近
            down--;
            //判断是否越界
            if(up > down){
                break;
            }
            //最左边一行
            for(int row = down; row >= up; row--){
                list.add(matrix[row][left]);
            }
            //向右逼近
            left++;
            //判断是否越界
            if(left > right){
                break;
            }
        }
        return list;
    }
}

20. 包含min函数的栈

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

解题思路:
双栈法。一个栈用来存放所有数据,另外使用辅助栈,每次把最小值保存在栈中。

//方法1:辅助栈中存放的是不包含重复元素的最小值
public class Solution {
    private static Stack<Integer> stack1 = new Stack<Integer>();//存放所有元素
    private static Stack<Integer> stack2 = new Stack<Integer>(); //作为辅助栈,保存最小值
    //入栈
    public void push(int node) {
        stack1.push(node);
        if(stack2.isEmpty() || stack2.peek() > node){
            stack2.push(node);//出现最小值才入栈,否则不操作
        }
    }
    //出栈
    public void pop() {
        if(stack1 != null){
            //不相等,说明当前弹出的不是最小值
            if(stack1.peek() != stack2.peek()){
                stack1.pop();
            }else{
                stack1.pop();
                stack2.pop();
            }
        }
    }
    //获取栈顶元素
    public int top() {
        return stack1.peek();
    }
    //获取最小元素
    public int min() {
        return stack2.peek();
    }
}
//方法2:辅助栈中存放的最小值是包含重复元素的
public class Solution {
    private static Stack<Integer> stack1 = new Stack<Integer>();//存放数据
    private static Stack<Integer> stack2 = new Stack<Integer>();//存放最小值
    public void push(int node) {
        stack1.push(node);
        if(stack2.isEmpty() || stack2.peek() > node){
            stack2.push(node);
        }else{
            stack2.push(stack2.peek());
        }
    }
    
    public void pop() {
        stack1.pop();
        stack2.pop();
    }
    
    public int top() {
        return stack1.peek();
    }
    
    public int min() {
        return stack2.peek();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值