[剑指offer] 11-23做题笔记

JZ11 二进制中1的个数

题目描述

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

示例1

输入

10

返回值

2

思路 直接比较

使用String类的toBinaryString函数将数字转换为二进制字符串,将字符串直接拆分后逐个与1进行比较。

public class Solution {
    public int NumberOf1(int n) {
        String s = Integer.toBinaryString(n);
        String[] split = s.split("");
        int ans = 0;
        for(int i = 0; i < split.length; i++){
            if(split[i].equals("1"))
                ans++;
        }
        return ans;
    }
}

JZ12 数值的整数次方

题目描述

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

保证base和exponent不同时为0

示例1

输入

2,3

返回值

8.00000

思路1:暴力解法

直接进行判断,针对exponent<0的情况,base取倒,exponent取正之后和>0的情况一样处理

public class Solution {
    public double Power(double base, int exponent){
        if(exponent == 0) return 1.0;
        else if(exponent > 0){
            double ret = base;
            while(exponent > 1){
                ret *= base;
                exponent--;
            }
            return ret;
        }else{
            base = 1.0/base;
            exponent = -exponent;
            double ret = base;
            while(exponent > 1){
                ret *= base;
                exponent--;
            }
            return ret;
        }
    }
}

思路2:递归快速幂

前面对0和负指数幂的处理是一样的,对于偶数幂,如x4可以写成(x2)2;而对于奇数x5可以写成(x2)2 * x,所以可以转为求x2,递归就出现了,针对奇数多乘一个x就好了。

public class Solution {
    public double q_power(double base, int exponent){
        if(exponent == 0) return 1.0;
        double ret = q_power(base, exponent/2);
        if((exponent&1)==1){//奇数,1与二进制表示的n的最后一位进行与,java中不能隐式转换int为boolean
            return ret * ret * base;
        }else{
            return ret * ret;
        }
    }
    public double Power(double base, int exponent){
        if(exponent < 0){
            base = 1 / base;
            exponent = -exponent;
        }
        return q_power(base, exponent);
    }
}

思路3:非递归快快速幂(STL中也是这种方法

假设求 x6,已知6可以表示成二进制110
可以表示成6=0 * 20+1 * 21 + 1 * 21,指数类似拆分x 6 = x 0 * 20+1 * 21 + 1 * 21,对于二进制数,遇到位数是1的就乘到答案中。
代码如下:

public class Solution {
    public double Power(double base, int exponent){
        //将指数按二进制写,为1的位乘进去
        if(exponent < 0){
            base = 1/base;
            exponent = -exponent;
        }
        double x = base;
        double ret = 1.0; //为0的情况一并写在这里
        while(exponent>0){
            if((exponent&1)==1){
                ret *= x; //为奇数就乘进去
            }
            //乘完后向高位移动一位
            x *= x;
            exponent >>= 1;
        }
        return ret;
    }
}

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

题目描述

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

思路:暴力遍历

直接三次循环,创建一个临时数组,第一次循环将所有奇数按序放入;第二次循环将所有偶数按序放入;第三次循环将临时数组的值放进答案数组中去

public class Solution {
    public void reOrderArray(int [] array) {
        int[] tmpArray = new int[array.length];
        int count = 0;
        for(int i = 0; i < array.length; i++){
            if((array[i]&1)==1){//奇数
                tmpArray[count++] = array[i];//count是当前空位位置标记,也可代表当前存放数量
            }
        }
        for(int i = 0; i < array.length; i++){
            if((array[i]&1)==0){//偶数
                tmpArray[count++] = array[i];
            }
        }
        for(int i = 0; i < array.length; i++){
            array[i] = tmpArray[i];
        }
    }
}

思路2:in-place算法

如果不开辟额外数组该怎么做呢?
初始化操作:记录一个变量i表示已经将奇数放好的下一个位置,显然最开始i=0,表示还没有一个奇数放好。
j 表示数组的下标,初始值为0, 表示从下标0开始遍历。

  1. 如果遇到偶数,j++
  2. 如果遇到奇数,假设位置为j,就将此奇数插入到i所指的位置,然后i往后移动一个位置,在插入之前,显然会涉及到数据的移动,也就是将[i,j-1]整体往后移动。
  3. 直到整个数组遍历完毕,结束
public class Solution {
    public void reOrderArray(int [] array) {
        int i = 0;
        for(int j=0; j < array.length; j++){
            if((array[j]&1)==1){//奇数
                int tmp = array[j];
                for(int k=j-1; k>=i; --k){
                    array[k+1] = array[k];
                }
                array[i++] = tmp;
            }
        }
    }
}

JZ14 链表中

题目描述

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

示例1

输入

1,{1,2,3,4,5}

返回值

{5}

思路:返回正向第n-k个位置

遍历一次获得链表长度,然后获取第n-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 cur = head;
        int length = 0;
        while(cur != null){
            cur = cur.next;
            length++;
        }
        if(length < k) return null;
        length -= k;
        while(length-- != 0){
            head = head.next;
        }
        return head;
    }
}

思路2:快慢指针

倒数多少位其实就有最后数几个箭头,可以设定为快慢指针步长k。slow指针设定为头,fast指针为离头k位的节点,快慢指针同步移动,fast指针到null(最后一位)的时候停止,此时slow指针所处位置即为我们希望获取的节点

/*
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;
        
        while(k-- != 0){
            if(fast != null)
                fast = fast.next;
            else
                return null;
        }
        while(fast != null){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

JZ15 反转链表

题目描述

输入一个链表,反转链表后,输出新链表的表头。

示例1

输入

{1,2,3}

返回值

{3,2,1}

至少四种方案:具体思想和图示见==>单链表反转详解

思路1:头插法

将head元素逐个放入以头插法建立的ans序列中

public class Solution {
    public ListNode ReverseList(ListNode head) {
        //使用头插法,建立新的序列
        if(head == null || head.next == null) return head;
        ListNode ans = null;
        while(head != null){
            ListNode tmp = head;
            //将tmp从head摘除
            head = head.next;
            
            //将tmp插入到ans的头部
            tmp.next = ans;
            ans = tmp;
        }
        return ans;
    }
}

思路2:迭代反转链表

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        else{
            //迭代反转链表,初始化三个指针,每次移动前,改变mid所指节点的指针域,使其只想和begin相同
            ListNode begin = null;
            ListNode mid = head;
            ListNode end = head.next;
            //一直遍历
            while(true){
                //修改mid的节点指向
                mid.next = begin;
                //判断end是否为null,是则立即退出循环
                if(end == null)
                    break;
                //整体向后移动一个单位
                begin = mid;
                mid = end;
                end = end.next;
            }
            //最后修改head头指针的指向
            head = mid;
            return head;
        }
    }
}

思路3:递归反转链表(暂时没看懂,mark)

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        else{
            //一直递归,找到链表最后一个节点
            ListNode ans = ReverseList(head.next);
            //逐层退出时,ans指向都不变,一直指向原链表中的最后一个节点
            //递归没退出一层,函数中的head指针的指向都会发生改变,都指向上一个节点
            
            //每退出一层,都需要改变head->next节点指针域的指向,同时令head所指节点的指针域为NULL
            head.next.next = head;
            head.next = null;
            //每一层递归结束,都要将新的指针返回给上一层。由此,即可保证整个递归过程中,能够一直找得到新链表的表头。
            return ans;
        }
    }
}

思路4:就地逆置反转链表

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode begin = head;
        ListNode end = head.next;
        while(end != null){
            //将end从链表中摘除
            begin.next = end.next;
            //将end移动到链表头
            end.next = head;
            head = end;
            //调整end的指向,令其指向beg后的一个节点,为反转下一个节点做准备
            end = begin.next;
        }
        return head;
    }
}

暂存

/*
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 && list2 != null) return list2;
        if(list1 != null && list1 == null) return list1;
        if(list1 == null && list2 == null) return null;
        if(list1.val <= list2.val){
            ListNode ans = new ListNode(list1.val);
            list1 = list1.next;
        }else{
            ListNode ans = new ListNode(list2.val);
            list2 = list2.next;
        }
        while(list1 != null || list2 != null){
            if(list1.val <= list2.val){
                ListNode tmp = new ListNode(list1.val);
                list1 = list1.next;
            }else{
                ListNode tmp = new ListNode(list2.val);
                list
            }
        }
    }
}

JZ16合并两个排序的链表

题目描述

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

示例1

输入

{1,3,5},{2,4,6}

返回值

{1,2,3,4,5,6}

思路1:迭代版本

设置哨兵,方便统一后续操作,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) {
        //哨兵节点vhead,方便统一后序操作顺序
        ListNode vhead = new ListNode(-1);
        ListNode cur = vhead;
        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;
        }
        //当有一个list为空的判断
        cur.next = list1 != null ? list1 : list2;
        return vhead.next;
    }
}

思路2:递归版本

主要想清楚结束条件和下一个递归区间

  • 结束条件:有一个list为空的时候
  • 下一个递归区间,和迭代方法类似,头最小的区间的下一个位置和另外一个list进行比较和合并。
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //当只有一个list为空的时候,返回不为空的list
        if(list1 == null) return list2;
        if(list2 == null) return list1;
        if(list1.val <= list2.val){
            list1.next = Merge(list1.next, list2);
            return list1;
        }else{
            list2.next = Merge(list1, list2.next);
            return list2;
        }
    }
}

JZ17 树的子结构

题目描述

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

示例1

输入

[复制](javascript:void(0)😉

{8,8,#,9,#,2,#,5},{8,9,#,2}

返回值

[复制](javascript:void(0)😉

true

思路 递归

主要分两步

  • 在大树中找到和小树根节点相同的节点
  • 以此节点为根节点,在大树往下搜索对比小树左右节点是否相同,不同则返回false;
  • 第二步中返回false则返回第一步,从大树的左子树找和小树根节点相同的节点
  • 如果第三步中最后返回false 回到第一步,从大树的右子树找和小树根节点相同的节点。
/**
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(root1 == null || root2 == null) return false;
        boolean result = false;
        if(root1.val == root2.val){
            result = HasSubTreeHelper(root1, root2);
        }
        if(!result) result = HasSubtree(root1.left, root2);
        if(!result) result = HasSubtree(root1.right, root2);
        return result;
    }
    
    //解释下这里两个if:第一个if 判断小树是否已经遍历完全,如果完全,说明前面各个节点的比较都是true,显然小树是大树的子结构。如果第一个if不成立,说明小树还要继续往下比较,这时第二个if 判断大树是否已经到达尽头,如果大树到达尽头,显然小树不是大树的子结构。 如果调换两个if,如果第一个if判断r1==null成立,返回false是不对的,r1和r2可以同时到达尽头,r1==null也可能是true的
    public boolean HasSubTreeHelper(TreeNode r1, TreeNode r2){
        if(r2 == null) return true;
        if(r1 == null) return false;
        if(r1.val != r2.val) return false;
        return HasSubTreeHelper(r1.left, r2.left) && HasSubTreeHelper(r1.right, r2.right);
    }
}

JZ18 顺时针打印矩阵

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

示例1

输入

[复制](javascript:void(0)😉

[[1,2],[3,4]]

返回值

[复制](javascript:void(0)😉

[1,2,4,3]

思路 每遍历一边缩小边界+边界检查

刷 LeetCode 看到的大神题解,感觉容易理解且好写
简单来说,就是不断地收缩矩阵的边界
定义四个变量代表范围,up、down、left、right

  1. 向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
  2. 向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
  3. 向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
  4. 向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错
import java.util.ArrayList;
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;//行数-1
        int left = 0;
        int right = matrix[0].length - 1;//列数-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;
    }
}

JZ19 包含min函数的栈

题目描述

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

思路

JZ21 栈的压入,弹出序列

题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

示例1

输入

[复制](javascript:void(0)😉

[1,2,3,4,5],[4,3,5,1,2]

返回值

[复制](javascript:void(0)😉

false

思路:遍历pushA序列

  • 如果pushA当前元素不等于popA当前元素,就将当前pushA的比较元素入栈
  • 如果等,说明入栈后立即出栈了,均向后移动一位即可,不用重复操作
  • 另一个是在栈非空的情况下,如果栈顶元素等于popA当前元素,直接出栈,popA比较下一位元素(这个判断可以放在最后,pushA判断结束后
  • 判断条件是上面操作完成后,栈中是否还有元素。
import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        //构造一个真正的栈,只要pushA[i] != popA[j] 就把pushA入栈。退出条件是,访问完入栈序列后,栈是否为空
        Stack<Integer> sta = new Stack<Integer>();
        
        int j = 0;//标识当前比较popA下标,
        int i = 0;//i是标识pushA下标
        while(i < pushA.length){
            if(pushA[i] != popA[j]){
                sta.push(new Integer(pushA[i++]));
            }else{
                //相等的时候就,一进一出,不用对栈进行操作,直接移动比较的下标
                i++;
                j++;
                while(!sta.empty() && sta.peek() == popA[j]){
                    sta.pop();
                    j++;
                }
            }
        }
//         while(!sta.empty() && sta.peek() == popA[j]){
//                     sta.pop();
//                     j++;
//                 }
        return sta.empty();
    }
}

JZ22 从上打印二叉树

题目描述

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

示例1

输入

[复制](javascript:void(0)😉

{5,4,#,3,#,2,#,1}

返回值

[复制](javascript:void(0)😉

[5,4,3,2,1]

思路:使用队列特性


从根节点开始,每次打印这个节点,就将左右子节点入队列,之后队列中每次出队都按照这样的顺序进行操作。

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;

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

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

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        ArrayList<Integer> res = new ArrayList<>();
        
        if(root == null)
            return res;
        
        deque.addLast(root);
        
        while(!deque.isEmpty()){
            TreeNode node = deque.getFirst();
            deque.pollFirst();
            res.add(node.val);
            
            if(node.left != null)
                deque.addLast(node.left);
            
            if(node.right != null)
                deque.addLast(node.right);
        }
       return res;
    }
}

JZ23 二叉排序树的后序遍历

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

示例1

输入

[复制](javascript:void(0)😉

[4,8,6,12,16,14,10]

返回值

[复制](javascript:void(0)😉

true

复习--二叉排序树

二叉排序树要么是空二叉树,要么具有如下特点:

  • 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;

  • 二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值;

  • 二叉排序树的左右子树也要求都是二叉排序树;

  • 例如,图 1 就是一个二叉排序树:

图 1 二叉排序树

c语言实现见-->c语言中文网

思路1:

public class Solution {

    public boolean helpVerify(int [] sequence, int start, int root){
        if(start >= root)return true;
        int key = sequence[root];
        int i;
        //找到左右子数的分界点
        for(i=start; i < root; i++)
            if(sequence[i] > key)
                break;
        //在右子树中判断是否含有小于root的值,如果有返回false
        for(int j = i; j < root; j++)
            if(sequence[j] < key)
                return false;
        return helpVerify(sequence, start, i-1) && helpVerify(sequence, i, root-1);
    }
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence == null || sequence.length == 0)return false;
        return  helpVerify(sequence, 0, sequence.length-1);

    }
}

思路2: 使用JZ21的思想,中序遍历为顺序排列,判断后序是否为中序出栈所得

评论区看到这种解法,感觉这种方法实际上是必要不充分,只能说牛客网测试用例还是不够严谨。只能说明后序遍历是出栈序列中的一种。但是牛客网和leetcode都过了

import java.util.Stack;
import java.util.Arrays;

//这里使用前面JZ21的思路,中序遍历是顺序排列的
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        int []arr = sequence.clone();
        Arrays.sort(arr);
        return IsPopOrder(arr, sequence);
    }
    
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length == 0 || popA.length == 0)
            return false;
        
        
        //构造一个真正的栈,只要pushA[i] != popA[j] 就把pushA入栈。退出条件是,访问完入栈序列后,栈是否为空
        Stack<Integer> sta = new Stack<Integer>();
        
        int j = 0;//标识当前比较popA下标,
        int i = 0;//i是标识pushA下标
        while(i < pushA.length){
            if(pushA[i] != popA[j]){
                sta.push(new Integer(pushA[i++]));
            }else{
                //相等的时候就,一进一出,不用对栈进行操作,直接移动比较的下标
                i++;
                j++;
                //将栈顶和popA比较
                while(!sta.empty() && sta.peek() == popA[j]){
                    sta.pop();
                    j++;
                }
            }
        }
        return sta.empty();
    }
}

自测截图,居然通过了,显然这不是后序遍历得到的

思路3:单调栈

leetcode题解

import java.util.Stack;

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0)
            return false;
        
        Stack<Integer> stack = new Stack<>();
        int root = Integer.MAX_VALUE;
        for(int i = sequence.length - 1; i >= 0; i--) {
            if(sequence[i] > root) return false;
            while(!stack.isEmpty() && stack.peek() > sequence[i])
            	root = stack.pop();
            stack.add(sequence[i]);
        }
        return true;
    }
}
posted @ 2021-02-23 15:16  lonelyisland  阅读(68)  评论(0编辑  收藏  举报