[剑指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开始遍历。
- 如果遇到偶数,j++
- 如果遇到奇数,假设位置为j,就将此奇数插入到i所指的位置,然后i往后移动一个位置,在插入之前,显然会涉及到数据的移动,也就是将[i,j-1]整体往后移动。
- 直到整个数组遍历完毕,结束
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
- 向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
- 向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
- 向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
- 向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 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:单调栈
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;
}
}