- 数组中重复的数字
- 二维数组中的查找
- 替换空格
- 从尾到头打印链表
- 重建二叉树
- 二叉树的下一个节点
- 用两个栈实现队列
- 斐波那契额数列
- 旋转数组的最小值
- 调整数组顺序使奇数位于偶数前面
- 链表中倒数第k个结点 --递归版
- 链表中环的入口节点
- 反转链表
- 合并两个排序的链表
- 树的子结构
- 二叉树的镜像
- 对称的二叉树
- 顺时针打印矩阵
- 包含min函数的栈
- 栈的压入、弹出序列
- 从上到下打印二叉树
- 按之字形顺序打印二叉树
- 从上往下打印二叉树
- 二叉搜索树的后序遍历序列
- 二叉树中和为某一值的路径
- 复杂链表的复制
- 序列化二叉树
- 字符串的排列
- * 多数元素
- 最小的k个数
- 数据流中的中位数
- 连续子数组的最大和
- 1出现的次数
- 数组排成最小的数
- 丑数
- 第一个只出现一次的字符
- 数组中的逆序对
- 两个链表的第一个公共节点
- 二叉搜索树的第k大节点
- 二叉树的深度
- 和为s的连续正数序列
- 和为s的两个数字
- 翻转单词顺序列/或者叫翻转字符串
- 幸存者(约瑟夫环)
数组中重复的数字
package basic_class_66;
/**
* 剑指 Offer 03. 数组中重复的数字 暴力:使用哈希表
* 方法一:遍历数组
* 由于只需要找出数组中任意一个重复的数字,因此遍历数组,遇到重复的数字即返回。
* 为了判断一个数字是否重复遇到,使用集合存储已经遇到的数字,如果遇到的一个数字已经在集合中,则当前的数字是重复数字。
*
* 初始化集合为空集合,重复的数字 ans = -1
* 遍历数组中的每个元素:
* 将该元素加入集合中,判断是否添加成功
* 如果添加失败,说明该元素已经在集合中,因此该元素是重复元素,将该元素的值赋给 ans,并结束遍历
* 返回 ans
*
* 作者:LeetCode-Solution
* 链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/mian-shi-ti-03-shu-zu-zhong-zhong-fu-de-shu-zi-b-4/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
import java.util.HashSet;
import java.util.*;
public class Solution_03_duplicate_baoli {
public static int findRepeatNumber(int[] nums) {
int ans =-1;
Set<Integer> set = new HashSet<Integer>();
for(int i=0;i<nums.length;i++){
if(!set.add(nums[i])){
ans= nums[i];
break;
}
}
return ans;
}
}
方法二
package basic_class_66;
/**
*
* 剑指 Offer 03. 数组中重复的数字
* 找出数组中重复的数字。
*
*
* 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
*
* 示例 1:
*
* 输入:
* [2, 3, 1, 0, 2, 5, 3]
* 输出:2 或 3
*
* 解法:
* 方法1:可以使用哈希表记录每个数字出现的次数,当发现出现次数>1,返回这个数就可以。需要开辟O(n)的空间。
*
* 方法2:根据题目条件:所有数字都在 0~n-1 的范围内,
* 可以维护一种关系;
* 数组下标index和数组中数字下标所保存的值,让他们两个相等。来找到重复的数字。例如:
* [ 2 3 1 0 2 5 3 ]
* 下标为0的位置是2,不等于0,则交换2和下标为2的数字即1,变为:
* [1 3 2 0 2 5 3]
* 来到了下标为1的位置,3是不等于1的,交换3和下标为3 的数字
* [1 0 2 3 2 5 3] 发现来到下标为2位置,2==2,3==3,来到下标4的位置,为2!=4,
* 那么就判断,以当前位置index=4,的数字2当作一个下标,去判断这个下标对应的数字是否等于当前值2,如果等于,说明当前位置的数字出现次数不少于1,
*
*
* 方法一:通过维护 nums[nums[i]] = nums[i]这个关系式,在对当前位置的数字进行遍历时,判断当前位置的数字是否满足前面的不等式,如果满足,说明当前的数字存在的次数超过1次(只有前面的维护过程中才能使得当前的数字满足等式)
*
*/
public class Solution_03_duplicate_leetcode {
public static int findRepeatNumber(int[] nums) {
int ans = -1;
for (int i = 0; i < nums.length; i++) {
// 1、当前位置值和当前位置的下标一样,那么就不需要维护了
if (nums[i] == i) {
continue; // 不需要去维护index和index对应的值相等的关系
}//如果当前位置值和当前位置的下标不相等
// 2、以当前位置的数字nums[i]作为下标即nums[nums[i]],在数组中找到下标nums[nums[i]]对应的数字,看是否和这个下标一样,如果一样,说明这个数字之前已经出现过,当前数字为重复数字
if (nums[nums[i]] == nums[i]) {
ans = nums[i];
break;
}//nums[nums[i]] != nums[i],则交换
// 下面的三行是交换两个数字,目的是为了维护 nums[nums[i]] = nums[i]
int temp = nums[i];//temp=2
nums[i] = nums[nums[i]];
nums[temp] = temp;//这里temp相当于nums[i]
}
return ans;
}
public static void main(String[] args) {
int[] a = new int[]{2, 3, 1, 0, 2, 5, 3};
System.out.println(findRepeatNumber(a));
}
}
nowcode
package basic_class_66;
/**
*
* 剑指 Offer 03. 数组中重复的数字
* 找出数组中重复的数字。
*
*
* 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
*
* 示例 1:
*
* 输入:
* [2, 3, 1, 0, 2, 5, 3]
* 输出:2 或 3
*
* 解法:
* 方法1:可以使用哈希表记录每个数字出现的次数,当发现出现次数>1,返回这个数就可以。需要开辟O(n)的空间。
*
* 方法2:根据题目条件:所有数字都在 0~n-1 的范围内,
* 可以维护一种关系;
* 数组下标index和数组中数字下标所保存的值,让他们两个相等。来找到重复的数字。例如:
* [ 2 3 1 0 2 5 3 ]
* 下标为0的位置是2,不等于0,则交换2和下标为2的数字即1,变为:
* [1 3 2 0 2 5 3]
* 来到了下标为1的位置,3是不等于1的,交换3和下标为3 的数字
* [1 0 2 3 2 5 3] 发现来到下标为2位置,2==2,3==3,来到下标4的位置,为2!=4,
* 那么就判断,以当前位置index=4,的数字2当作一个下标,去判断这个下标对应的数字是否等于当前值2,如果等于,说明当前位置的数字出现次数不少于1,
*
*
* 方法一:通过维护 nums[nums[i]] = nums[i]这个关系式,在对当前位置的数字进行遍历时,判断当前位置的数字是否满足前面的不等式,如果满足,说明当前的数字存在的次数超过1次(只有前面的维护过程中才能使得当前的数字满足等式)
* 原地置换
*
* 由于长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内,所以可以让数字放入到这个数字对应的下标位置,
*
* 遍历数组,
*
* 1.如果下标和对应的值相等,不用调整
*
* 2.如果下标和对应的值不相等,那么把当前的值作为下标,如果发现这个下标对应的值相等,则重复;
*
* 3.如果下标和对应的值不相等,那么把当前的值作为下标,如果发现这个下标对应的值不相等,则交换;
*/
public class Solution_03_duplicate_nowcoder {
public boolean duplicate(int nums[],int length,int [] duplication) {
int ans = -1;
for (int i = 0; i <length; i++) {
// 1、当前位置值和当前位置的下标一样,那么就不需要维护了
if (nums[i] == i) {
continue; // 不需要去维护index和index对应的值相等的关系
}//如果当前位置值和当前位置的下标不相等
// 2、以当前位置的数字nums[i]作为下标即nums[nums[i]],在数组中找到下标nums[nums[i]]对应的数字,看是否和这个下标一样,如果一样,说明这个数字之前已经出现过,当前数字为重复数字
if (nums[nums[i]] == nums[i]) {
ans = nums[i];
break;
}
// 3. nums[nums[i]] != nums[i],则交换
// 下面的三行是交换两个数字,目的是为了维护 nums[nums[i]] = nums[i]
int temp = nums[i];//temp=2
nums[i] = nums[nums[i]];
nums[temp] = temp;//这里temp相当于nums[i]
}
duplication[0] = ans;//这个数组的第0个位置等于ans
return duplication[0]!=-1;//说明ans是已经更新过的,说明ans出现次数是大于1的,如果等于-1说明没有找到一个数字出现次数大于1,返回false
}
}
nocoder1
package basic_class_66;
public class Solution_03_duplicate_nowcoder01 {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers==null||length<=0){
return false;
}
for(int i=0;i<length;i++){//length即相当于numbers.length
while(numbers[i]!=i){
if(numbers[i]==numbers[numbers[i]]){
duplication[0]=numbers[i];
return true;
}
swap(numbers,i,numbers[i]);
}
}
return false;
}
public static void swap(int[] array,int i,int j){
int temp= array[i];
array[i] = array[j];
array[j] = temp;
}
}
二维数组中的查找
package basic_class_66;
/**
* 从左下角开始查找 --推荐
*/
public class Solution_04_TwoSrrayFind_03 {
public boolean findNumberIn2DArray(int[][] array, int target) {
int i=array.length-1;
int j=0;
while(i>=0 && j<array[0].length){
if(array[i][j]==target){
return true;
}else if(array[i][j]>target){
i--;
}else{
j++;
}
}
return false;
}
}
替换空格
package basic_class_66;
import java.util.Scanner;
/**
* 剑指 Offer 05. 替换空格
* 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
*
* 示例 1:
*
* 输入:s = "We are happy."
* 输出:"We%20are%20happy."
* Created by sudo on 2020/8/5.
*/
public class Solution_05_ReplaceSpace {
public static String replaceSpace(StringBuffer str) {
//sb用于增加%20字符串
StringBuffer sb = new StringBuffer();
String replaceStr = "%20";
for (int i = 0; i < str.length(); i++) {
//字符串中获取下标为i的字符CharAt
// 三元表达式写法
sb.append(str.charAt(i)==' '?replaceStr:str.charAt(i));
//// 不优化写法
// if (str.charAt(i) == ' ') {
// sb.append(replaceStr);
// } else {
// sb.append(str.charAt(i));
// }
}
//StringBuffer类型转为String类型
return sb.toString();
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
StringBuffer str = new StringBuffer();
str.append(sc.nextLine());
/**
* 读取字符串
* nextLine()
* next() 遇到空格就停止
*/
System.out.println(replaceSpace(str));
}
}
从尾到头打印链表
package basic_class_66;
import java.util.ArrayList;
/**
* 剑指 Offer 06. 从尾到头打印链表
* <p>
* 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
* 方法二:递归的方式
* 模拟链表反置效果
*/
public class Solution_06_PrintListFromTailToHead_02_leetcode {
ArrayList<Integer> list = new ArrayList<>();
public int[] reversePrint(ListNode head) {
solve(head);
//list->array
int[] ans = new int[list.size()];
for (int i = 0; i < ans.length; i++) {
ans[i] = list.get(i);
}
return ans;
}
public void solve(ListNode head){
if (head == null) {
return;
}
reversePrint(head.next);
list.add(head.val);//将当前节点值加入列表
}
}
非递归
package basic_class_66;
import java.util.ArrayList;
import java.util.Stack;
/**
* 剑指 Offer 06. 从尾到头打印链表
*
* 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
* <p>
* 方法一:使用栈
* 思路:
* 如果边遍历边保存,得到的是从头到尾的顺序。
* 这样的反置的效果,可以通过栈或递归的方式来实现
* <p>
* 实现:
* 定义一个栈,
* 遍历链表,传入了listnode;当链表不空,将当前节点值放入到栈,并更新listnode值,要不然陷入死循环
* <p>
* 判断栈是否为空,如果不空,则把栈顶元素取出来,放入list里面
* 直到栈空了,说明栈中元素都放入list里去了。
*/
public class Solution_06_PrintListFromTailToHead_01_leetcode {
public int[] reversePrint(ListNode node) {
ArrayList<Integer> list = new ArrayList<>();
Stack<Integer> stack = new Stack<Integer>();
while (node != null) {
stack.add(node.val);//取当前节点的值放入栈中
node = node.next;//更新当前节点为下一个节点
}
while (!stack.isEmpty()) {
list.add(stack.pop());//取出当前栈顶元素,然后放入list中
}
//把list->int[]array list转为数组
int[] ans = new int[list.size()];
for(int i=0;i<list.size();i++){
ans[i] = list.get(i);
}
return ans;
}
}
重建二叉树
package basic_class_66;
/**
* 重建二叉树
* <p>
* 递归实现
* 题目描述
* 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
* 假设输入的前序遍历和中序遍历的结果中都不含重复的数字
* 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和
* 中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
* <p>
* 二叉树的先序(preorder),中序(inorder),后序(postorder)
*/
public class Solution_07_rebuildTree {
private static int index = 0;//遍历前序序列的下标
public static TreeNode func(int[] preorder, int[] inorder) {
if (preorder.length == 0) {
return null;
}
// len1 = 3,len2 =4 说明在help里面,1的左边有三个值,1的右边有4个值
int len1 = 0;//当前节点的左子树的节点的个数
int len2 = 0;//当前节点的右子树的节点的个数
//在inorder里面找出1的位置
for (int i = 0; i < inorder.length; i++) {
if (preorder[index] == inorder[i]) {
break;
}//在没有跳出这个for循环时,第i个位置的左边都是当前节点的左子树
len1++;//左子树节点的个数++
}
// 下面是把1的左子树和右子树的节点保存到temp1,和temp2里面
len2 = inorder.length - len1 - 1;//-1是减去当前的节点
int[] temp1 = new int[len1];//当前节点的左子树
int[] temp2 = new int[len2];//当前节点的右子树
// 1、func方法:统计当前节点的左子树和右子树都有哪些(创建左子树和右子树)
// 往temp1,temp2两个子序列中加值
boolean flag = false;
int index1 = 0;
int index2 = 0;
for (int i = 0; i < inorder.length; i++) {
if (preorder[index] == inorder[i]) {
flag = true;
} else if (!flag) {//说明还没找到inorder[i]和pre中index位置相等,则放入到左子树temp1中
temp1[index1++] = inorder[i];
} else { //即flag=true,说明已经找到了这个节点,再往后遍历就相当于是当前节点(比如1)的右子树,所以放入到temp2的数组中
temp2[index2++] = inorder[i];
}
}
// 2,将当前节点的左子树和右子树传入到func方法中,继续去创建左子树和右子树
TreeNode node = new TreeNode(preorder[index]);
node.right = null;
node.left = null;
// 测试打印左子树和右子树
// System.out.printf("%d左子树:", preorder[index]);
// for (int i = 0; i < temp1.length; i++) {
// System.out.printf("%d ", temp1[i]);
// }
// System.out.print("; ");
// System.out.printf("%d右子树:", preorder[index]);
// for (int i = 0; i < temp2.length; i++) {
// System.out.printf("%d ", temp2[i]);
// }
// System.out.println();
/**
* index <pre.length 即遍历前序序列的下标,还没有到头
* temp1.length>0:即当前节点1的左子树是有节点的,则继续递归左子树
*
*/
if (index < preorder.length && temp1.length > 0) {
index++;//遍历前序序列的下标加1
//temp1 4 7 2 再传入func,对当前节点1的左子树进行构建
node.left = func(preorder, temp1);//创建当前节点的左子树
}
if (index < preorder.length && temp2.length > 0) {
index++;//遍历前序序列的下标加1
node.right = func(preorder, temp2);//创建当前节点的右子树
}//当前节点的左子树和右子树都进行了遍历,再把当前节点返回给上一层
return node;
}
public static TreeNode reConstructBinaryTree(int[] preorder, int[] inorder) {
index = 0;//遍历前序序列的下标
return func(preorder, inorder);
}
public static void main(String[] args) {
int[] preorder = {1, 2, 4, 7, 3, 5, 6, 8};
int[] inorder = {4, 7, 2, 1, 5, 3, 8, 6};
TreeNode root = reConstructBinaryTree(preorder, inorder);
dfs1(root);
System.out.println();
dfs2(root);
System.out.println();
dfs3(root);
System.out.println();
}
/**
* 先序遍历
* 先输出当前节点的值
* 然后遍历左孩子,再遍历右孩子
*
* @param node
*/
private static void dfs1(TreeNode node) {
System.out.printf("%d ", node.val);
if (node.left != null) {
dfs1(node.left);
}
if (node.right != null) {
dfs1(node.right);
}
}
/***
* 中序遍历
* 先去遍历当前节点的左孩子,
* 再打印当前节点
* 再右孩子
* @param node
*/
private static void dfs2(TreeNode node) {
if (node.left != null) {
dfs2(node.left);
}
System.out.printf("%d ", node.val);
if (node.right != null) {
dfs2(node.right);
}
}
/**
* 后序遍历
* 遍历完左孩子,遍历完右孩子
* 最后打印当前节点
*
* @param node
*/
private static void dfs3(TreeNode node) {
if (node.left != null) {
dfs3(node.left);
}
if (node.right != null) {
dfs3(node.right);
}
System.out.printf("%d ", node.val);
}
}
二叉树的下一个节点
package basic_class_66;
/**
* 二叉树的下一个节点
* 题目描述
* 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
* 方法一:主要是分为三种情况,第一种情况就是pNode节点有右孩子时,那么pNode的下一个节点就是右孩子对应的那颗子树的最左侧的节点;如果说当前节点的右孩子为空,并且pNode是pNode父亲节点的左孩子,那么直接返回pNode的父亲节点即可;如果说当前节点的右孩子为空,并且pNode是pNode父亲节点的右孩子那么就返回pNode节点的爷爷节点。当然还有些特殊情况,比如说:二叉树的最右侧节点的判断,以及父亲节点是否为空的判断。
*/
public class Solution_08_GetNext {
public TreeLinkNode GetNext(TreeLinkNode pNode) {
if (pNode.right != null) {
// 第一种情况,pNode节点的右孩子不为空
pNode = pNode.right;
while (pNode.left != null) {
pNode = pNode.left;
}
return pNode;
} else {
TreeLinkNode tempNode = pNode.next;
if (tempNode == null) {
return null;
}
if (tempNode.left == pNode) {
// 第二种情况,当前节点右孩子为空,并且当前节点是父亲节点的左孩子
return tempNode;
} else {
// 第二种情况,当前节点右孩子为空,并且当前节点是父亲节点的右孩子
boolean flag = false;
while (tempNode.next != null) {
if (tempNode.next.left == tempNode) {
flag = true;
break;
}
tempNode = tempNode.next;
}
return flag ? tempNode.next : null; // flag尾true时,说明pNode所指的节点不是二叉树中最右侧节点
}
}
}
}
用两个栈实现队列
package basic_class_66;
/**
* 用两个栈实现队列
*
* 题目描述
* 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
*
* 思路:
* stack1
* stack2
* push操作都在stack1里面完成;
* pop操作都在stack2里面完成;
* push时候,如果栈2里面有元素,需要将栈2中的元素取出来放到栈1里面,然后将node节点元素放入到栈1里面
* pop的时候,如果栈1里面有元素,需要将栈1里面的元素取出来放到栈2里面,再执行pop操作。
*/
import java.util.Stack;
public class Solution_09_StackPushPop_impl_Queue {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
/**
* push时候,如果栈2里面有元素,需要将栈2中的元素取出来放到栈1里面,然后将node节点元素放入到栈1里面
* @param node
*/
public void push(int node) {
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
stack1.push(node);
}
/**
* pop的时候,如果栈1里面有元素,需要将栈1里面的元素取出来放到栈2里面,然后将栈2的头返回即可。
* @return
*/
public int pop() {
while (!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.pop();
}
}
斐波那契额数列
package basic_class_66;
/**
* 斐波那契额数列
* 方法一:采用递推的方式去求出a[n]的值
*/
public class Solution_10_Fibonacci_method01 {
public int Fibonacci(int n) {
if (n == 0) {
return 0;
}
int[] a = new int[n + 1];
if (n == 1 || n == 2) {
return 1;
}
a[1] = 1;
a[2] = 1;
for (int i = 3; i <= n; i++) {
a[i] = a[i - 1] + a[i - 2];
}
return a[n];
}
}
递归
package basic_class_66;
/**
* 斐波那契额数列
* 方法二:采用递归的方式去求出a[n]的值
*/
public class Solution_10_Fibonacci_method02 {
public int Fibonacci(int n) {
if (n == 0) {
return 0; /// 终止递归的条件
}
if (n == 1 || n == 2) {
return 1; /// 终止递归的条件
}
return Fibonacci(n - 1) + Fibonacci(n -2);
}
}
旋转数组的最小值
package basic_class_66;
/**
*
* 11 旋转数组的最小值
*
* 题目描述
* 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
* 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
* 例如数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。
* NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
*
*
* 方法1:暴力
* 遍历数组,遍历同时用一个变量保存最小值,然后不断的去更新这个值,但是并没有用到非递减的条件
*
* 方法2:原来数组拆分成两个子数组,然后二分
* 【3,4,5】【,1,2】
*/
public class Solution_11_minNumberInRotateArray_baoli {
public int minNumberInRotateArray(int [] array) {
if (array.length==0){
return 0;
}
int ans = array[0];//数组第一个元素设为ans,然后从数组的array[1]开始遍历,去不断的更新这个变量
for(int i=1;i<array.length;i++){
ans = Math.min(ans,array[i]);
}
return ans;
}
}
调整数组顺序使奇数位于偶数前面
package basic_class_66;
import java.util.*;
public class Solution_21_reOrderArray_twoArray {
/**
* 调整数组顺序使奇数位于偶数前面
* <p>
* 题目描述
* 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
* <p>
* 方法2:
* 定义两个数组,分别保存奇数和偶数,最终合并两个数组 O(n)
* <p>
* 本质是:开辟两个空间去存储奇数和偶数,最终将这两个空间中的值合并即可。
*
* 具体实现:
* 遍历数组过程中,判断每个位置的奇偶性,然后把每个位置放入到对应的数组中,
* 然后把两个集合合并即可
*/
public void reOrderArray(int[] array) {
ArrayList<Integer> list1 = new ArrayList<Integer>();//保存奇数
ArrayList<Integer> list2 = new ArrayList<Integer>();//保存偶数
for (int i = 0; i < array.length; i++) {
if (array[i] % 2 != 0) {//如果是奇数,就将当前数字添加到list1
list1.add(array[i]);
} else {//如果是偶数,就将当前数字添加到list2
list2.add(array[i]);
}
}
int index = 0;//用来遍历更新array数组下标的值
for (int x : list1) {//在list1里面取出一个值放入到x里面
array[index++] = x;
}
for (int x : list2) {
array[index++] = x;
}
}
}
链表中倒数第k个结点 --递归版
package basic_class_66;
/**
*
* 链表中倒数第k个结点 --递归版
*
* 题目描述
* 输入一个链表,输出该链表中倒数第k个结点
*
*/
public class Solution_22_FindKthToTail_recursionMethod {
private ListNode ans; /// 最终返回的结果
private int sum; /// 用来记录当前节点是倒数第几个节点
private void dfs(ListNode node, int k) {
if (node.next != null) {
dfs(node.next, k); /// 继续递归下一个节点。
}
// 判断当前层的节点是倒数第几个节点。
sum++;
if (sum == k) {//说明当前层的node节点是倒数第k个节点
ans = node;
}
}
public ListNode FindKthToTail(ListNode head, int k) {
ans = null;
sum = 0;
if (head == null) { /// 说明链表为null,就没有必要去递归的需要了
return null;
}
dfs(head, k); /// 递归遍历链表
return ans;
}
}
非递归
package basic_class_66;
/**
*
* 链表中倒数第k个结点 --双指针(最优解法)
*
* 题目描述
* 输入一个链表,输出该链表中倒数第k个结点
*
* 思路:
* 指针1先走k-1步,然后指针2和指针1同时前进,当指针1指向链表最后一个元素时,指针2即为所求
*
* 设置两个节点:
* 设置两个移动节点,第一个移动节点指向head,即头节点,第二个节点和第一个节点距离为k,
* 由于移动过程中,两个节点移动步伐一样。这样当第二个移动节点到达链表尾部时候,移动节点1距离链表的
* 尾部一定是k,即倒数第k个节点。
*
* // 1、定义移动节点2
* ListNode node2 = head;//移动节点2
* // 2、让移动节点2和移动节点1(即头节点)之间距离为k
* while(k!=0){
* node2 = node2.next;
* k--;
* 注意:如果k大于链表长度,其实是返回null;
* 所以如果我发现node2==null,直接返回null就行了,没有必要再移动两个节点。这时候移动节点2已经到了链表尾部。
*
*/
public class Solution_22_FindKthToTail_TwoPointMethod {
public ListNode FindKthToTail(ListNode head,int k){
// 1、定义两个移动指针,,都从头节点开始
ListNode slow = head;//移动节点1
ListNode fast = head;//移动节点2
// 2、先让快指针走k-1步
for(int i=0;i<k;i++){//即让fast先走k-1步
if(fast==null){//如果快指针为空,直接返回空
return null;
}
fast = fast.next;
}
// 3、两指针同时移动,直到快指针为空的时候,即走到了尾部,这时慢指针指向的就是倒数第k个元素
while(fast!=null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
链表中环的入口节点
package basic_class_66;
import java.util.HashMap;
import java.util.Map;
/**
*
* 链表中环的入口节点
*
*
* 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null
* map结构保存环中结点出现的次数
*
* 遍历的时候,在没有把环中结点遍历完的情况下是不可能到达环的入口结点。
* 把环中结点遍历完之后,发现遍历当前结点出现的次数为2。
*
*/
public class Solution_23_EntryNodeOfLoop {
public ListNode EntryNodeOfLoop(ListNode pHead){
Map<ListNode,Integer> map = new HashMap<>();
ListNode node = pHead;
// 遍历链表的时候,先去更新当前节点出现的次数
while (node!=null){
map.put(node,map.getOrDefault(node,0)+1);
//更新完出现的次数之后,就判断以下,
if (map.get(node)==2){
return node;
}
node = node.next;
}//遍历完链表之后,发现还没有一个出现次数为2的节点,说明当前链表中没有环,返回null
return null;
}
}
反转链表
package basic_class_66;
/**
* 剑指 Offer 24. 反转链表
* 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
*
* 示例:
*
* 输入: 1->2->3->4->5->NULL
* 输出: 5->4->3->2->1->NULL
*
* 输入一个链表,反转链表后,输出新链表的表头
*
* 思路:(3种方法:递归、栈、双指针)
* 方法三:设置两个移动节点,距离为1
* 让链表上原来相邻的两个节点的指向给逆过来。
*
* 关键点: ,达到两个节点的指向逆过来
*
*/
public class Solution_24_ReverseList_01 {
public ListNode ReverseList(ListNode head) {
//首先判断,如果head为空,就没必要进行反转了
if (head == null) {
return null;
}
//如果链表不为空,首先设置两个移动节点
ListNode node1 = head;//移动节点1
ListNode node2 = head.next;//移动节点2
//操作两个移动节点,进行翻转
while (node2 != null) {
/*对于第一次翻转,有个坑,就是将移动节点2所指向的节点的next指向head
所以说如果直接将移动节点2的next指向移动节点1的话,那么在下一次遍历的时候
就找不到节点,就相当于这条路断了。相当于把动节点2和后面节点分离开了
所以在移动节点2所指向的节点的next指向head(移动节点1指向的节点)时候,
还是需要一个ListNode类型的变量去保存当前移动节点2原来所指向的下一个节点
*/
ListNode tempNode = node2.next;//用来保存移动节点的下一个节点,不然会造成节点最终无法往右移情况
//实现节点反置
node2.next = node1;//相当于把移动节点2的next给指向前一个
//这时候相当于翻转操作已经结束了,然后需要把两个节点更新,即同时往右移动1位
//实现两个节点的向右平移
node1 = node2;//即移动节点1到了移动节点2的位置
node2 = tempNode; //移动节点2就等于原来移动节点2的下一个位置
}
//原始链表的head没有删除,如果不删除,会出现死循环的情况
head.next = null;
return node1;//最终返回node1
}
}
合并两个排序的链表
package basic_class_66;
/*
* 剑指 Offer 25. 合并两个排序的链表
* 题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
*
* 思路:
* 类似于归并排序中子序列合并的思想(整体外排)
*
* 通过移动三个指针(链表的下标):
* 遍历链表1 的指针1((黑色)
* 遍历链表2 的指针2(红色)
* 指针3:通过一个指针将合并后的链表创建出来(绿色):指向合并后的链表的尾部
*
* 比较办法:
* 链表1和链表2中的指针没有到达链表末尾的时候,比较每个链表中当前位置的指针指向的数字
* 第一次0和1:因为0<1,所以合并之后的链表头部就是0,可以采用尾接法去创建合并后的链表,肯定需要一个尾指针,
* 用蓝色箭头的指针去表示。
*
* 第一次比较:
* 第一次0和1:因为0<1,所以合并之后的链表头部就是0,由于比较之和,0已经放入到合并之后的链表了,
* 接下来操作其实就是需要把链表1中的指针往后平移1步;即移到了2的位置;
*
* 接下来是2和1的比较;1<2; => 即链表2头部的值是小于链表1中指针指向的值,说明合并之后的链表下一个位置
* 肯定是链表2的头节点;
* 根据尾接法,我们需要将合并之后链表的尾部节点的next指向第二个链表的头节点;
* 同时需要更新合并之后链表的尾节点的指向。(尾节点的指向是需要不断更新的)
* 由于1<2, 所以需要将指向1的指针往后平移1位;
*
* 同理:再比较2,3,显然2<3;则 合并之后的链表的尾节点的next即1指向2,由于2是小于3的,所以指向2的指针往后
* 移动1位,就指向了空值,这时候在两个链表比较的过程中,就没有必要再进行下去了,因为其中一个链表已经全部的放入到
* 合并之后的链表当中了。(即已经把链表1中的0和2放入到合并之后的链表中了 )
*
* 也就是当黑色指针和红色指针不为空,就一直比较链表1和链表2中指针所指向的节点值
* 跳出while循环:链表1还是2完全放入合并后的链表了。
*
*
* 建议:写下归并排序(和两个数组最后的外排过程思路是一样的)
*
*
* 总结:
* 合并两个排序的链表:
* 方法一:类似于归并排序中子序列合并过程,不断去比较两个链表中节点的val值;
* 然后去判断哪个节点优先需要添加到合成链表的尾部。
*
* */
public class Solution_25_MergeTwo_orderList {
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
//确定最终合并的链表的头节点
ListNode headNode;//最终合并的链表的头节点
if (list1.val > list2.val) {
headNode = list2;
list2 = list2.next;
} else {
headNode = list1;
list1 = list1.next;
}
//定义尾节点removeNode 即绿色指针
ListNode removeNode = headNode;//其实在当前位置就是合成链表的长度为1,它的头节点和尾节点是一样的
while (list1 != null && list2 != null) {
if (list1.val > list2.val) {
//1、将list2中的节点添加到了合并链表的尾部
removeNode.next = list2;//将合成链表的尾部节点添加链表2中当前所指向的节点
//2、然后去更新合成链表的尾部节点,指针3
removeNode = list2;//去更新合成链表的尾部节点
//3、因为list2中已经把一个节点放入到合成链表了,所以去更新 遍历链表2的指针(红色)
list2 = list2.next;
} else {
//将list2放到了合并链表的尾部
removeNode.next = list1;//将合成链表的尾部节点添加链表2中当前所指向的节点
removeNode = list1;//去更新合成链表的尾部节点
list1 = list1.next;
}
}
// 将剩余的链表1中的节点放入到合成链表中
while (list1 != null) {
removeNode.next = list1;
removeNode = list1;
list1 = list1.next;
}
// 将剩余的链表2中的节点放入到合成链表中
while (list2 != null) {
removeNode.next = list2;
removeNode = list2;
list2 = list2.next;
}
return headNode;//返回合成链表的头节点
}
}
树的子结构
package basic_class_66;
/**
* 树的子结构
* 题目描述
* 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
* <p>
* 思路:
* 1、比较二叉树A中的每一个节点与二叉树B的root节点进行比较
* 2、如果二叉树A的当前节点与二叉树B的root节点的val一致,那么就抽离出以二叉树A的当前节点为root的子树,进行比较
* <p>
* 只需要找到节点左子树或右子树匹配就说明找到了
* <p>
* 总结:
* 1-先序遍历,遍历过程中进行查找(匹配的入口)
* 比较二叉树当前节点的val值跟第二个二叉树的root节点的val值是否一样,
* 如果一样,就进入到第二部分比较过程(进行匹配)。其实就是同时遍历两颗二叉树的左孩子同时遍历两颗二叉树右孩子。
* 然后把两颗二叉树的左孩子匹配的结果和两颗二叉树的右孩子匹配的结果进行与运算,然后返回上一层
* <p>
* 如果不一样,即当前节点val不等于另外一个棵树的root,就递归到左子树,递归到右子树,
* 就比较当前节点的左孩子节点,去比较当前节点的val值和另外一个棵树的root节点是否一样,
* 同理,比较右孩子;
* 当左孩子右孩子都比较完之后,会返回左右子树的结果,flag1、flag2。
* 只需要左子树或者右子树匹配即可。匹配成功就可以了。
* 所以,有个剪枝过程。如果左子树匹配,那么就没有必要进行右子树比较了。
*
*
* 方法一:主要是分为两个过程,过程一就是查找过程,只有在查找过程中当前A中节点的val等于B中root节点val值一样时,才会进入到匹配过程,
* 匹配过程的话对两个二叉树就是采用同样的匹配方式去比较每次递归的节点的val是否一样。然后去判断两个二叉树结构是否一样。
*/
public class Solution_26_HasSubtree {
/**
* 二叉树的先序遍历
*
* @param node
* @param root2
* @return
*/
public boolean dfs(TreeNode node, TreeNode root2) {//第一部分,先序遍历过程进行查找,找匹配入口
boolean flag = false;//保存同时遍历两颗二叉树的左孩子和右孩子匹配结果
// 1、比较当前节点和另一棵树的root节点是否匹配
if (node.val == root2.val) {//如果一样,就进入到第二部分比较过程(进行匹配)。其实就是同时遍历两颗二叉树的左孩子,同时遍历两颗二叉树右孩子
flag = judge(node, root2);
}
if (flag) {
return true; /// 通过当前节点已经找到二叉树B的完全匹配结果了,就没有必要再往下去遍历二叉树A了。也可以说是剪枝的一个过程
}
// 2、如果当前节点和第二颗树的root节点不相等,遍历当前节点的左孩子、右孩子
boolean flag1 = false; /// 默认不匹配,用来记录当前节点的左子树中的查找结果(其实也是包含了匹配的过程),如果查找成功(包含了匹配过程)返回true
boolean flag2 = false; /// 用来记录当前节点的右子树中的查找结果(其实也是包含了匹配的过程),如果查找成功(包含了匹配过程)返回true
if (node.left != null) {
flag1 = dfs(node.left, root2);/// 当前节点的val不等于二叉树B的root值,那么就去遍历当前节点的左子树,看否找到二叉树B
}
if ((!flag1) && node.right != null) {//!flag1-》剪枝,如果左子树不匹配了,才有必要在右子树进行匹配
flag2 = dfs(node.right, root2);//当前节点的val不等于二叉树B的root值,那么就去遍历当前节点的右子树,看否找到二叉树B
}
// 2.1、返回左子树和右子树匹配结果
return flag1 || flag2;/// || -》只需要找到节点的某一个方向的子树进行匹配成功就行了
}
/**
* 第二部分,左子树和右子树都完全匹配成功,才算是子结构,即匹配成功
* 同时遍历两颗二叉树的左孩子同时遍历两颗二叉树右孩子,然后返回匹配结果flag1,flag2
*
* @param node1
* @param node2
* @return
*/
public boolean judge(TreeNode node1, TreeNode node2) {
if (node2 == null) {
return true;//说明二叉树 B 某一个方向的节点已匹配完成(越过叶子节点),因此返回 true
}
if (node1 == null) {
return false;//即节点A为空,说明在某一方向上,已经越过树 A 叶子节点(即相对B而言,A中缺少匹配的节点),即匹配失败,返回 false
}
if (node1.val == node2.val) {//当节点 A 和 B 的值相同:说明匹配成功,则考察树A当前节点node1的左孩子和树B当前节点node2的左孩子是否相等;同理两者当前节点的的右孩子
boolean flag1 = true;/// 默认左子树是匹配的,假如说不匹配,它就会返回false
boolean flag2 = true;/// 默认右子树是匹配的,假如说不匹配,它就会返回false
if (node1.left != null || node2.left != null) {
flag1 = judge(node1.left, node2.left);/// 比较子树A和二叉树B的左子树
}
if (flag1 && (node1.right != null || node2.right != null)) {/// flag1 -> 剪枝,就是如果左半部分不匹配,则右边再怎么匹配,以当前节点为根节点的二叉树不可能和树B匹配。
flag2 = judge(node1.right, node2.right);/// 比较子树A和二叉树B的右子树
}
return flag1 && flag2;/// && -> 不光某一个节点的左子树要完全匹配,右子树也是要完全匹配的
} else {//当节点 A 和 B 的值不同:说明匹配失败,返回false
return false;
}
}
public boolean HasSubtree(TreeNode root1, TreeNode root2) {
if (root1 == null || root2 == null) {/// root1 == null -> 就是二叉树A就是一颗空树, root2 -> 约定空树不是任意一个树的子结构
return false;
}
return dfs(root1, root2);//第一部分,通过遍历,进行比较
}
}
二叉树的镜像
package basic_class_66;
/**
*
* 二叉树的镜像
*
* 请完成一个函数,输入一个二叉树,该函数输出它的镜像。
*
* 例如输入:
*
* 4
* /
* 2 7
* / /
* 1 3 6 9
* 镜像输出:
*
* 4
* /
* 7 2
* / /
* 9 6 3 1
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*
*
*/
public class Solution_27_mirrorTree {
public TreeNode mirrorTree(TreeNode node){
if(node!=null){
// 后续遍历
if(node.left!=null){
mirrorTree(node.left);
}
if(node.right!=null){
mirrorTree(node.right);
}
// 交换左右孩子
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
return node;
}
}
对称的二叉树
package basic_class_66;
/*
剑指 Offer 28. 对称的二叉树
* 描述
请实现一个函数,用来判断一棵二叉树是不是对称的。
* 注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
*
* 思路:
* 判断左子树折过去是否和右子树一样
* 定义两个节点node1,node2分别遍历两颗子树
* 判断当前两个变量所指向的节点的value是否一样。
*
* 关键点:怎么去更新这两个节点的值,来去比较这两颗子树对应的位置是一样的。
*
* */
public class Solution_28_isSymmetrical {
public boolean isSymmetrical(TreeNode pRoot) {
if (pRoot == null) {
return true;
}
return solve(pRoot.left, pRoot.right);
}
private boolean solve(TreeNode node1, TreeNode node2) {
/*
* 就是我在去找这两个子树对应的位置的时候,我已经找到了叶子节点了。
* 由于是从上往下找的,那么在找到叶子节点之后,从下往上返回的时候,返回true
* 就说明我从递归开始的节点出发,一直到叶子节点的这条路径。左右这两颗子树所对应的
* 这两条路径上的节点的value值都是一样的。
* */
if (node1 == null && node2 == null) {
return true;
}
//node1和node2不同时为空,即两颗子树长度不同,这种情况都返回false。
if (node1 == null || node2 == null) {
return false;
}
if (node1.val != node2.val) {//2,2
return false;
}
// 递归的去查询两个子树对应的val是否一样
return solve(node1.left, node2.right) && solve(node1.right, node2.left);
}
}
顺时针打印矩阵
package basic_class_66;
/**
* 顺时针打印矩阵
* 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
* 示例 1:
* <p>
* 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
* 输出:[1,2,3,6,9,8,7,4,5]
* 示例 2:
* <p>
* 输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
* 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
* 限制:
* <p>
* 0 <= matrix.length <= 100
* 0 <= matrix[i].length <= 100
* <p>
* 思路:
* 通过一个flag变量不断的去更新遍历矩阵下标 x,y 的值;(通过越界和flag当前的值进行更新的)
* 模拟:遍历过程
* flag变量:去标记当前遍历矩阵的方向
* 1 往右
* 2 往下
* 3 往左
* 4 往上
*/
import java.util.ArrayList;
public class Solution_29_printMatrix {
public ArrayList<Integer> printMarix(int[][] matrix) {
ArrayList<Integer> ans = new ArrayList<>();//返回结果
int flag = 1;//默认1;1-right; 2-down, 3-left,4-up
int x = 0;//下标(x,y)从(0,0)开始
int y = 0;
// 遍历
boolean[][] vis = new boolean[matrix.length][matrix[0].length];//用来标记已经走过的点,boolean默认为false
while (ans.size() < matrix.length * matrix[0].length) {//矩阵元素个数matrix.length*matrix[0].length
/*
* x<0 左
* x>=matrix.length 下
* y<0 上
* y>matrix[0].length 右
*
* vis[x][y]代表已经遍历过的位置也当作越界处理
* */
// y>=matrix[0].length,因为从0开始的,当y=matrix[0].length时候,已经越界了
if (x < 0 || x >= matrix.length || y < 0 || y >= matrix[0].length || vis[x][y]) {//越界
if (flag == 1) {//往右走的时候越界了,肯定要往下走
flag = 2;//更新flag
y--;//消除越界的影响
x++;//本质上是到达下一个位置的横坐标
} else if (flag == 2) {//往下走的时候越界了,肯定要往左走
flag = 3;//往左走
x--;//消除越界影响
y--;//本质上是到达下一个位置的纵坐标
} else if (flag == 3) {//往左走的时候越界了,肯定要往上走
flag = 4;//往上走
y++;//消除越界影响
x--;//本质上到达下一个位置的横坐标
} else {//flag==4情况:往上走的时候越界了,肯定要往右走
flag = 1;
x++;//消除越界影响
y++;//本质上是到达下一个位置的纵坐标
}
} else {//说明当前还没有超出矩阵边界
ans.add(matrix[x][y]);//当前位置的值添加到ans
vis[x][y] = true;//标记已经遍历过的位置
// 下一次所走的位置,根据flag去判断方向;更新遍历矩阵的下标,x,y的值
if (flag == 1) {//往右走,即列++
y++;
} else if (flag == 2) {
x++;
} else if (flag == 3) {
y--;
} else if (flag == 4) {
x--;
}
}
}
return ans;
}
}
包含min函数的栈
package basic_class_66;
import java.util.Stack;
/**
* 包含min函数的栈
* <p>
* 题目描述
* 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
* <p>
*
* 方法:
* 维护两个栈;
* 第一个栈就是普通的数据栈;
* 第二个栈含义:就是在建立第一个栈的基础上,比如说第一个栈中有n个元素,
* 那么第二个栈所要维护的就是第一个栈中这n个元素的最小值
*/
public class Solution_30_MinStack {
//定义两个栈
private Stack<Integer> dataStack = new Stack<>();//数据栈
private Stack<Integer> minStack = new Stack<>();//维护min函数的栈
public void push(int node) {
dataStack.push(node);
// 当前最小栈的栈顶元素大于数据栈的栈顶元素
if (minStack.isEmpty() || minStack.peek() > dataStack.peek()) {//4,2 ;4>2,取最小值2;加入minStack.isEmpty()是当最小栈为空的时候,就不会去调用peek了,要不然报空指针异常
minStack.push(dataStack.peek());
} else {//当前最小栈的栈顶元素大于数据栈的栈顶元素
minStack.push(minStack.peek());//比如栈中元素是 4 2 1,栈顶是1,现在放入5,由于5>1,所以维护这4个数最小值的时候,最小栈里面肯定添加1,这个1是来自于没有添加5之前,最小栈的栈顶元素
}
}
/**
* 弹栈
* 假如数据栈不为空,就pop出来
*/
public void pop() {
if (dataStack != null) {
dataStack.pop();
}
if (!minStack.isEmpty()) {//当栈不为空的时候,就pop
minStack.pop();
}
}
// 取出数据栈的栈顶元素
public int top() {//取出数据栈的栈顶
return dataStack.peek();//光取不删
}
// 取出最小栈的栈顶元素
public int min() {//取出min函数的额栈顶元素
return minStack.peek();
}
}
栈的压入、弹出序列
package basic_class_66;
import java.util.Stack;
/**
* 栈的压入、弹出序列
* <p>
* 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
* 例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,
* 但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
* <p>
* 栈:
* 出栈数字肯定是在顶部
*/
public class Solution_31_validateStackSequences {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<Integer>();
int pushIndex = 0;//入栈序列的下标
int popIndex = 0;//出栈序列的下标
// 入栈序列的下标小于入栈序列的长度
while (pushIndex < pushed.length) {//当pushIndex下标等于pushed.length,说明这个栈里面元素都push完了
/*
判断对当前pushIndex对应的值,是push呢还是pop呢,
* 去判断当前栈顶元素是否等于当前出栈序列所指的元素,如果不等于,就继续往里push,如果相等,就pop
* */
if (!stack.isEmpty() && stack.peek() == popped[popIndex]) {
stack.pop();
popIndex++;//下标后移
} else {
stack.push(pushed[pushIndex]);
pushIndex++;
}
}
// 跳出循环后,pushIndex < pushed.length,即:入栈序列的下标等于入栈序列的长度
// 下面的这个while循环其实就是为了防止当所有入栈的元素都压入栈的时候,栈顶元素和出栈序列的下标所指的数字没有来得及比较。
// 当入栈序列的数字全部压入栈之后,需要判断栈顶元素和出栈序列下标所指的数字比较
while (!stack.isEmpty()) {
if (stack.peek() == popped[popIndex]) {//如果栈顶元素等于出栈序列的popIndex所指的元素,就将
stack.pop();
popIndex++;
} else {
return false;
}
}
return true;
}
}
从上到下打印二叉树
package basic_class_66;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
* leetcode:
* 剑指 Offer 32 - I. 从上到下打印二叉树
* 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
* <p>
* <p>
* <p>
* 例如:
* 给定二叉树: [3,9,20,null,null,15,7],
* <p>
* 3
* /
* 9 20
* /
* 15 7
* 返回:
* <p>
* [3,9,20,15,7]
* <p>
*
* 首先这个是把二叉树中的所有节点都当作一个数组来打印了。
*
* 思路:队列来保存每层节点 FIFO
*
* * 1
* * /
* * 2 3
* /
* 4 5
*
* 第一次迭代过程:首先队列中初始位置为1;取出1之后,删除1,再没有放入1的左右孩子之前,队列是空的,先放入1的左孩子2,再放入1的右孩子3
* 第二次迭代过程:取出队头2,并删除,然后把2的左右孩子放入队列中,即3 4 5
* 同理,取出3,然后发现3的左右孩子是空,则两个if条件就不会成立,即
* if (node.left != null) {
* queue.add(node.left);
* }
* if (node.right != null) {
* queue.add(node.right);
* }
* 不会成立,所以队列头为3的时候,只是取出3,再删除3,然后把3放入到list里,再去继续下一次迭代
*
* 下一次迭代,4取出并删除,添加到list中,左右孩子是空,进行下一次迭代
* 5取出并删除,保存到list中
*
*
*/
public class Solution_32_fromTopToBottomPrintTree_leftToRight {
public int[] levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
//返回数组类型,数组长度不知道,定义一个ArrayList
ArrayList<Integer> list = new ArrayList<>();
// 用队列来维护,遍历出每一个节点
while (!queue.isEmpty() && root != null) {
TreeNode node = queue.poll();//poll()取出之后删除
list.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
//保存打印的结果到数组里
int[] ans = new int[list.size()];
int index = 0;
for (int x : list) {
ans[index++] = x;
}
return ans;
}
}
打印二叉树
package basic_class_66;
/**
* * leetcode:
* * 剑指 Offer 32 - I. 从上到下打印二叉树
* * 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
*/
import java.util.LinkedList;
import java.util.Queue;
/**
* leetcode:
* 剑指 Offer 32 - II. 从上到下打印二叉树 II
* 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
*
*
*
* 例如:
* 给定二叉树: [3,9,20,null,null,15,7],
*
* 3
* /
* 9 20
* /
* 15 7
* 返回其层次遍历结果:
*
* [
* [3],
* [9,20],
* [15,7]
* ]
*
*
* 思路:
* 对于同一层,打印顺序不变,只是对于每一层当作一个数组。不能把所有节点当作一个数组。
* 对于每一层,需要去判断遍历完当前节点,这一层是否已经完了。即判断当前节点是否是当前层最后一个节点
*
* 实现:
* 通过一个遍历来去标记当前层的节点有没有被遍历完。
*
* 1
* /
* 2 3
/ /
4 5 6 7
当我们取出1的时候,放入2,3,其实可以判断出2,3就是第二层;
同理,取出2,3,当我们往队列中添加元素的时候,再添加元素个数的时候,就可以判断出添加的元素属于第三层
比如说遍历第二层节点时候,通过if往队列中添加节点(其实是第三层)时候,这时候是可以知道每一层节点的个数
就是根据当前层,往队列中添加左右孩子的时候,根据两个if来判断出当前层的下一层有多少个节点
是需要设置变量temp,在两个if里面进行temp++,来保存当前层的下一层节点个数
*/
import java.util.*;
public class Solution_32_fromTopToBottomPrintTree_oneLine {
public List<List<Integer>> levelOrder(TreeNode root){
List<List<Integer>> ans = new LinkedList<>(); //返回结果
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int sum =1;//设置变量;二叉树的根在第一层,节点个数为1;sum用来保存每一层的节点个数
while (!queue.isEmpty() && root != null) {
List<Integer> list = new LinkedList<>();//用来保存当前层的节点
int temp = 0;//用来保存遍历当前层的下一层节点个数
/**
* 这个while循环控制的是:对于每一层的遍历
* 结束之后,把 sum = temp;
*/
while (sum>0){//当sum>0,说明当前所遍历的节点所在的那一层节点还没有遍历完
// 遍历节点:把节点放入队列中
TreeNode node = queue.poll();//取节点
assert node!=null;//其实不会有空,这里判断一下
list.add(node.val);
// 取出当前节点后,往队列添加左右孩子
if (node.left!=null){
temp++;//保存当前层的下一层节点个数
queue.add(node.left);
}
if (node.right!=null){
temp++;//保存当前层的下一层节点个数
queue.add(node.right);
}
sum--;//说明当前所遍历的节点已经遍历过了,即当前层还没有遍历的节点要减少1个
}
sum = temp;//用于遍历下一层节点
ans.add(list);
}
return ans;
}
}
按之字形顺序打印二叉树
package basic_class_66;
/**
* 按之字形顺序打印二叉树
* 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,
* 第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
*
* 思路:
* 使用队列保存每一层节点 FIFO,所以把靠左的节点放入,就优先出来
* 1、从队列中取出当前节点,然后再删除当前节点
* 2、把当前节点左右孩子放入队列
*
*/
import java.util.*;
public class Solution_32_levelOrder {
/**
* levelOrder 层次遍历,宽度优先搜索
* @param root
* @return
*/
public int[] levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();//定义队列
queue.add(root);//初始化队列,即初始化root
ArrayList<Integer> list = new ArrayList<>();//定义不定长数组,保存返回结果
// BFS 宽搜
while(!queue.isEmpty() && root!=null){//root!=null:防止把root加入队列之后,调用node.val报空指针异常
// 1、第一步取出当前队列的头,再把它从队列中删除,并把头的左孩子和右孩子依次放入队列,如果是空节点,就没必要去遍历
TreeNode node = queue.poll();
list.add(node.val);
if (node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
int[] ans = new int[list.size()];//最终返回数组类型
int index=0;
for(int x:list){
ans[index++] = x;
}
return ans;
}
}
从上往下打印二叉树
package basic_class_66;
/**
* 从上往下打印二叉树
* Created by sudo on 2020/8/5.
*
* 从上往下打印出二叉树的每个节点,同层节点从左至右打印。
*
*/
import java.util.*;
/**
* BFS 用队列保存节点元素 FIFO
*/
public class Solution_32_PrintFromTopToBottom {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
//放入遍历二叉树的节点,目的是维护BFS
Queue<TreeNode> queue = new LinkedList<>();
//如果为空,返回arrlist即可
if (root != null) {
queue.add(root);
}
//迭代过程,BFS
while (!queue.isEmpty()) {
//接收当前队列的头部,光取不删
TreeNode node = queue.peek();
//当前元素的val放入ArrayList
arrayList.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
//当前这个节点已经遍历过了,也放入到ArrayList了,所以要从队列中删除
queue.poll();
}
return arrayList;
}
}
二叉搜索树的后序遍历序列
package basic_class_66;
/**
* 二叉搜索树的后序遍历序列
* <p>
* 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
* <p>
*
* <p>
* 参考以下这颗二叉搜索树:
* <p>
5
/
2 6
/
1 3
* 示例 1:
* <p>
* 输入: [1,6,3,2,5]
* 输出: false
* 示例 2:
* <p>
* 输入: [1,3,2,6,5]
* 输出: true
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*
*
* 解法:
* 二叉树搜索树:
* 对于某一个节点,它的左子树中所有数都是小于当前节点的val,它的右子树中所有的值都是大于当前节点的val值。
*
* 题目中只给出了一个序列,根据后续遍历来推出这个二叉树
* 1 3 2 5 4
* =》末尾节点4为根节点,val是4
* 二叉搜索树的根节点左子树小于4,右子树大于4,所以分成两个子序列 132和 5
* 对于132,也是一颗子树,末尾数字2就是这个子树的根,所以2为4左孩子,5为4右孩子,然后
* 1在2左侧,3在2的右侧(右子树)
*
*
* 用到的性质:
* 1、后续遍历的末尾是根节点,以及子序列的末尾也是子树的根
* 2、二叉搜索树的左子树val都小于当前节点,右子树节点val都大于当前节点的val
*
* 判断是否是二叉搜索树的序列:(判断方式)
* 如果在遍历过程中,发现某一个子序列中,根据末尾的位置去判断,大于末尾的数字的序列和小于末尾的数字的序列,这个两个序列如果不连续,
* 则说明不是二叉搜索树的序列。因为后续遍历先左子树,再右子树,最后根
*
* 思路:
* 不断的根据末尾数字划分当前序列,
* 然后判断划分完的两个序列是否为连续的,如果不连续,返回false,
* 如果连续,继续划分。
*
*/
import java.util.*;
public class Solution_33_verifyPostorder {
public boolean solve(ArrayList<Integer> list) {
/**
* list.size()==1 说明当前list里面的节点是叶子节点
* list.size()==0 说明调用递归函数的时候, solve(minList)&&solve(maxList);,传入的某一个ArrayList是空值,
* 其实就是对应某个节点只有左子树没有右子树;或者只有右子树没有左子树。
*/
// 递归终止条件
if (list.size() == 0 || list.size() == 1) {
return true;
}
ArrayList<Integer> minList = new ArrayList<Integer>();// 用来保存小于endNumber数字的序列
ArrayList<Integer> maxList = new ArrayList<Integer>();// 用来保存大于endNumber数字的序列
int endNum = list.get(list.size() - 1);
//使用两个遍历来记录list.get(i)>endNum以及小于,的初始位置。通过比较初始位置的关系,就可以判断出每个序列是否连续
int minIndex = -1;// 用来记录minList中第一个数字的位置
int maxIndex = -1;// 用来记录maxList中第一个数字的位置
// 下面这个循环其实就是对当前list序列的一个分割(分割条件就是endNumber)
for (int i = 0; i < list.size(); i++) {//序列的划分
if (list.get(i) > endNum) {
if (maxIndex == -1) {
maxIndex = i;
}
maxList.add(list.get(i));
} else if (list.get(i) < endNum){
if (minIndex == -1) {
minIndex = i;
}
minList.add(list.get(i));
}
}
// 判断两个序列是否连续
/**
* 因为后续遍历,左右根,所以如果出现大于endNum的在左边,小于endNum在右边这样的子序列,则不是二叉搜索树。
* 即: if(minIndex>maxIndex){
* return false;
* }
*/
if (minIndex != -1 && maxIndex != -1) {//说明都不为空
if (minIndex > maxIndex) {//情况1:大于endNum的在左边,小于endNum在右边这样的子序列,则不是二叉搜索树
return false;// 本质上使右子树的序列在左子树的前面,不满足后序遍历
}
//情况2:判断子序列连续性
//说明如果是二叉搜索树,则minIndex<maxIndex,所以从[minIndex,maxIndex-1]都是小于maxIndex
// 所以从[maxIndex,list.size()]都是大于endNum
for (int i = maxIndex; i < list.size(); i++) {
if (list.get(i) < endNum) {
return false; // 说明在大于endNumber的序列初始位置到末尾,不连续,中间有小于endNumber的数字分割开来
}
}
}
//如果都不满足,再去根据endNum划分当前序列为两个子序列,再去判断这两个子序列是否满足条件,递归
return solve(minList) && solve(maxList);// && -> 每一个子序列都是需要满足的
}
public boolean verifyPostorder(int[] Postorder) {
if (Postorder.length == 0) {//空树也是二叉搜素树,牛客网上的不严谨
return true;
}
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < Postorder.length; i++) {
list.add(Postorder[i]);
}
return solve(list);
}
}
二叉树中和为某一值的路径
package basic_class_66;
import java.util.ArrayList;
/**
* 剑指 Offer 34. 二叉树中和为某一值的路径
* * 题目:
* * 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。
* * 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径
* *
* * 考察点:
* * 二叉树的递归遍历,遍历过程中边遍历,边统计路径上的值
* * 并且这个路径指:从根节点开始,到叶子节点结束形成一个的一条路线才是一条路径。
* * (如果没到叶子节点,不能称为一条路径)
* *
* * 注意:(在返回值的list中,数组长度大的数组靠前 ) --即数组长度越大,在list里面是越靠前的
* * ArrayList<ArrayList<Integer>> 返回的相当于是一个二维数组
* * 二维数组中,第一个下标代表路径的下标;
* * 第二个下标代表每一条路径中,每一个节点的值
* *
* * 这道题其实:
* * 就是对一个树的DFS,深搜,在深搜遍历过程中,去判断当前所走的路径,它的权值之和,用一个变量记录下来。
* * 当我走到叶子节点的时候,我去判断当前记录的这个权值之和是否等于target。
* * 如果等于,就将当前路径记录下来。
* * 如果不等于,再进行回溯。
* *
* *
* * 在返回值的list中,数组长度大的数组靠前:
* * 当我们找到所有的路径之后,这个路径的长度其实有长有短,
* * 我们其实是类似先序遍历进行搜索的,所以搜索过程中,并不保证所有路径都是按照长度递减顺序保存在
* * ans数组里的,所以最终需要对ans进行排序。这里采用选择排序
*/
public class Solution_34_FindPath_01 {
/*
* 一般将最终返回结果定义成类的成员变量,当成全局变量去处理
* */
private ArrayList<ArrayList<Integer>> ans;//类的成员变量,最终要返回的二维数组
/**
* @param node 二叉树节点
* @param target 目标权值和
* @param sum 当前路径的权值和
* @param list 保存当前路径
* 功能:
* 找到所有权值和等于target的路径
*/
private void solve(TreeNode node, int target, int sum, ArrayList<Integer> list) {
//遍历过程先判断
if (node != null) {//不为空,就把当前节点加入到当前路径当中,同时把当前节点加入list中
sum += node.val;
list.add(node.val);
if (node.left == null && node.right == null) {//说明当前node节点是叶子节点,就去判断sum
if (sum == target) {//说明我当前已经找到了一条路径是满足sum==target的,此时
/*
* 相当于把list拷贝了一份,重新创建了一个空间,因为java里面对于map,ArrayList之类的,
* 传的是引用,如果对list进行修改的话,也是会修改它内存里面的值的,所以我们需要再去重新
* 开辟一个空间,去保存当前的值。
* */
//ArrayList是引用传递,所以需要通过res去重新开辟空间,去保存当前路径,再将当前路径添加到ans
ArrayList<Integer> res = new ArrayList<>(list);//相当于把list拷贝了一份
ans.add(res);//将当前路径添加到ans
} else {//如果if条件不满足,就先对左子树进行遍历,再对右子树进行遍历
solve(node.left, target, sum, list);//递归左子树
solve(node.right, target, sum, list);//递归右子树
}
// 当都递归完之后,需要从当前节点回溯到上一层(这时候需要把当前节点产生的影响消除)
//消除掉当前节点对查找路径的影响
// sum -= node.val;
/*
* sum是可以不用减的,因为传入sum的时候,传入的是一个int类型的值,
* 对于每一层递归,sum都是不一样的,所以sum -= node.val;意义不大
* 但是对于list,是需要将最后一个节点元素移出去的,
* 因为最后一个节点元素其实就是当前node节点的值,
* 当回溯到上一层的时候,需要将当前node节点的值从路径中删除掉。
* 其实就相当于把这个节点清除掉
* */
list.remove(list.size() - 1);//至关重要
}
}
}
private void change() {
for (int i = 0; i < ans.size(); i++) {//对每一个位置的遍历
int index = i;
for (int j = i + 1; j < ans.size(); j++) {//找到对应位置的节点元素
if (ans.get(j).size() > ans.get(index).size()) {
index = j;
}
}
//如果找到了,就交换下
if (i != index) {
ArrayList<Integer> temp = ans.get(i);
ans.set(i, ans.get(index));
ans.set(index, temp);
}
}
}
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
ans = new ArrayList<ArrayList<Integer>>();//对ans进行初始化
ArrayList<Integer> list = new ArrayList<>();
solve(root, target, 0, list);
change();
return ans;
}
}
方法二
package basic_class_66;
import java.util.ArrayList;
/**
* * 剑指 Offer 34. 二叉树中和为某一值的路径
* * 题目:
* * 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。
* * 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径
* *
* * 考察点:
* * 二叉树的递归遍历,遍历过程中边遍历,边统计路径上的值
* * 并且这个路径指:从根节点开始,到叶子节点结束形成一个的一条路线才是一条路径。
* * (如果没到叶子节点,不能称为一条路径)
* *
* * 注意:(在返回值的list中,数组长度大的数组靠前 ) --即数组长度越大,在list里面是越靠前的
* * ArrayList<ArrayList<Integer>> 返回的相当于是一个二维数组
* * 二维数组中,第一个下标代表路径的下标;
* * 第二个下标代表每一条路径中,每一个节点的值
* *
* * 这道题其实:
* * 就是对一个树的DFS,深搜,在深搜遍历过程中,去判断当前所走的路径,它的权值之和,用一个变量记录下来。
* * 当我走到叶子节点的时候,我去判断当前记录的这个权值之和是否等于target。
* * 如果等于,就将当前路径记录下来。
* * 如果不等于,再进行回溯。
* *
* *
* * 在返回值的list中,数组长度大的数组靠前:
* * 当我们找到所有的路径之后,这个路径的长度其实有长有短,
* * 我们其实是类似先序遍历进行搜索的,所以搜索过程中,并不保证所有路径都是按照长度递减顺序保存在
* * ans数组里的,所以最终需要对ans进行排序。这里采用选择排序
*/
public class Solution_34_FindPath_02 {
/*
* 一般将最终返回结果定义成类的成员变量,当成全局变量去处理
* */
private ArrayList<ArrayList<Integer>> ans;//类的成员变量,最终要返回的二维数组
/**
* @param node 二叉树节点
* @param target 目标权值和
* @param sum 当前路径的权值和
* @param list 保存当前路径
* 功能:
* 找到所有权值和等于target的路径
*/
private void solve(TreeNode node, int target, int sum, ArrayList<Integer> list) {
//遍历过程先判断
if (node != null) {//不为空,就把当前节点加入到当前路径当中,同时把当前节点加入list中
sum += node.val;
list.add(node.val);
if (node.left == null && node.right == null) {//说明当前node节点是叶子节点,就去判断sum
if (sum == target) {//说明我当前已经找到了一条路径是满足sum==target的,此时
/*
* 相当于把list拷贝了一份,重新创建了一个空间,因为java里面对于map,ArrayList之类的,
* 传的是引用,如果对list进行修改的话,也是会修改它内存里面的值的,所以我们需要再去重新
* 开辟一个空间,去保存当前的值。
* */
//ArrayList是引用传递,所以需要通过res去重新开辟空间,去保存当前路径,再将当前路径添加到ans
ArrayList<Integer> res = new ArrayList<>(list);//相当于把list拷贝了一份
ans.add(res);//将当前路径添加到ans
}
} else {//如果if条件不满足,就先对左子树进行遍历,再对右子树进行遍历
solve(node.left, target, sum, list);//递归左子树
solve(node.right, target, sum, list);//递归右子树
}
// 当都递归完之后,需要从当前节点回溯到上一层(这时候需要把当前节点产生的影响消除)
//消除掉当前节点对查找路径的影响
// sum -= node.val;
/*
* sum是可以不用减的,因为传入sum的时候,传入的是一个int类型的值,
* 对于每一层递归,sum都是不一样的,所以sum -= node.val;意义不大
* 但是对于list,是需要将最后一个节点元素移出去的,
* 因为最后一个节点元素其实就是当前node节点的值,
* 当回溯到上一层的时候,需要将当前node节点的值从路径中删除掉。
* 其实就相当于把这个节点清除掉
* */
list.remove(list.size() - 1);//至关重要
}
}
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
ans = new ArrayList<ArrayList<Integer>>();//对ans进行初始化
ArrayList<Integer> list = new ArrayList<>();
solve(root, target, 0, list);
return ans;
}
}
复杂链表的复制
package basic_class_66;
/**
* 复杂链表的复制
*
* O(n)
*
* 考察点:哈希表
* 题目描述
* 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
*/
import java.util.*;
public class Solution_35_copyRandomList_method01 {
public RandomListNode copyRandomList(RandomListNode head) {
// 1.遍历当前节点,为链表中每一个节点去创建新空间中的节点,并且保存原新节点的对应关系
// 去创建新链表中的节点元素和原链表节点元素与新链表节点元素之间的映射关系
Map<RandomListNode, RandomListNode> map = new HashMap<>();
RandomListNode removeNode = head;//当前遍历的节点
while (removeNode != null) {//遍历当前节点,为链表中每一个节点去创建新空间中的节点
RandomListNode node = new RandomListNode(removeNode.val);
map.put(removeNode, node);//原链表中节点和新链表中节点的对应关系
removeNode = removeNode.next;
}
// 2.第二次遍历:去创建新链表中节点之间的关系
// 去创建新链表中每个节点的结构关系(根据原链表的节点的结构关系)
removeNode = head;
while (removeNode != null) {//当前节点不等于空
/**
* 当前节点不为空的时候,
* 1、获取原链表中当前节点对应的新链表节点
* 2、next:先找到原链表中的next,然后通过map映射关系找到新链表中node的next对应的位置
* random也是如此
*/
RandomListNode node = map.get(removeNode);//用node保存原链表中节点元素所对应的新链表中节点元素
node.next = map.get(removeNode.next);//removeNode.next是原链表中的next
node.random = map.get(removeNode.random);
removeNode = removeNode.next;
}
//head即原链表的头节点,原链表的头节点所指向的节点即新链表的头节点的位置
return map.getOrDefault(head, null);//如果传入的head是空的,就直接去null就可以
}
}
优化
package basic_class_66;
/**
* 复杂链表的复制 方法二,节省空间
* <p>
* 考察点:优化
* 题目描述
* 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),
* 请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
* <p>
* 思路:三个过程:
* <p>
* 1、完成新链表节点的插入,就相当于确定了新链表中每一个节点元素的位置
* 1-1'-2-2'-3—3'
* <p>
* 2、找random节点即可
* 让1的random指向3,则让1'的random也指向3'
* 3、链表分割:奇数和偶数
*/
public class Solution_35_copyRandomList_method02 {
public RandomListNode copyRandomList(RandomListNode head) {
if(head==null){
return null;
}
//第一个过程:创建新链表节点插入到原链表中
RandomListNode removeNode = head;
while (removeNode != null) {
//首先需要一个临时节点来保存下一次移动的节点,比如第一次遍历1,下一次直接就跳动2了,所以需要遍历1的时候,保存2的位置
RandomListNode temp = removeNode.next;
RandomListNode node = new RandomListNode(removeNode.val);
removeNode.next = node;//原节点指向新节点1-1'
node.next = temp;//新节点指向当前节点的next
removeNode = temp;
}
//第二个过程:创建random节点指向
/**
* 如果说当前节点有指向,则新节点的指向和原节点额指向是一样的,比如1-3;则1'-3'
* 如果原节点没有指向,则新节点置为null即可
*
* 其中removeNode.next.random = removeNode.random == null ? null : removeNode.random.next;
* 因为removeNode是从头节点开始,所以原链表中每个节点的next相当于新链表中的节点,即removeNode.next
* 所以新链表中节点的random,即removeNode.next.random,它就等于原链表中random的指向,即removeNode.random
* 如果等于空,就null,如果不等于空,新链表中random的指向就相当于原链表中random指向的next,比如
* 1-1'-2-2'-3-3'
* 1的random指向3,则1'的random就指向3的next即3'
*
*/
removeNode = head;//初始化移动节点
while (removeNode != null) {
removeNode.next.random = removeNode.random == null ? null : removeNode.random.next;
// 然后遍历原链表中下一个节点,相当于让原链表中节点后移
removeNode = removeNode.next.next; // 用两个next是把新链表节点隔过去,因为只需要对原链表进行遍历
}
// 第三个过程->链表的分割
removeNode = head;//初始化移动节点
RandomListNode cloneHead = head.next;//克隆节点的头,其实就是1'的位置
while (removeNode != null) {//遍历原链表不等于null
RandomListNode node = removeNode.next;//保存当前节点的next,其实在每次迭代过程中相当于新链表的节点
//1-1'-2-2'-3-3';相当于1的next指向2,建立了1指向2这条边
removeNode.next = node.next; // 原链表中节点的结构之间关系的维护; 其实1-1'-2-2',这里node相当于1'的位置;removeNode相当于1;
/**
* 1-1'-2-2'-3-3'
* 比如遍历到3'了,3'的next就是null,所以需要判断node.next是否为空的
*/
node.next = node.next == null ? null : node.next.next;// 维护新链表中节点关系的维护,相当于1'指向2'这条边
removeNode = removeNode.next;//后移操作,因为前面removeNode.next = node.next;已经对removeNode.next已经更新过了,下次进行后移其实到2的位置了
}
return cloneHead;
}
}
序列化二叉树
package basic_class_66;
/**
* 剑指 Offer 37. 序列化二叉树
* 请实现两个函数,分别用来序列化和反序列化二叉树。
*
* 示例:
*
* 你可以将以下二叉树:
*
* 1
* /
* 2 3
* /
* 4 5
*
* 序列化为 "[1,2,3,null,null,4,5]"
*
* 方法一:序列化操作是通过对二叉树宽搜,如果当前节点是null的话,然后判断当前节点的后面的节点当中是否有非空节点,如果有,就将null写入序列胡结果,反置不写入。反序列化操作也是采用宽搜的方式,对于最终的二叉树的节点逐个的去创建。
*/
import java.util.*;
public class Solution_37_SerializeBinaryTree {
public static String serialize(TreeNode root) {
StringBuilder ans = new StringBuilder("[");
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int sum = 1; // 用来记录当前节点及其后面非空节点的个数
while (!queue.isEmpty() && root != null) {
TreeNode node = queue.poll();
if (node == null) {
ans.append("null");
} else {
ans.append(node.val);
sum--;
if (node.left != null) {
sum++;
}
if (node.right != null) {
sum++;
}
queue.add(node.left);
queue.add(node.right);
}
if (sum != 0) {
ans.append(",");
} else {
break;
}
}
ans.append("]");
return ans.toString();
}
// Decodes your encoded data to tree.
public static TreeNode deserialize(String data) {
String s = data.substring(1, data.length() - 1);
if ("".equals(s)) {
return null; // data = "[]"
}
String[] a = s.split(",");
int index = 0;
Queue<TreeNode> queue = new LinkedList<>();
TreeNode root = new TreeNode(change(a[index++]));
queue.add(root);
while (!queue.isEmpty() && index < a.length) {
TreeNode node = queue.poll();
if (!"null".equals(a[index])) {
node.left = new TreeNode(change(a[index++]));
queue.add(node.left);
} else {
index++;
}
if (index < a.length && !"null".equals(a[index])) {
node.right = new TreeNode(change(a[index++]));
queue.add(node.right);
} else {
index++;
}
}
return root;
}
private static int change(String s) {
int res = 0;
int i = 0;
int flag = 1;
if (s.charAt(0) == '-') {
i++;
flag = -1;
}
for (; i < s.length(); i++) {
res = res * 10 + s.charAt(i) - '0';
}
return res * flag;
}
}
字符串的排列
package basic_class_66;
/**
* 剑指 Offer 38. 字符串的排列
* 输入一个字符串,打印出该字符串中字符的所有排列。
*
*
*
* 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
*
*
*
* 示例:
*
* 输入:s = "abc"
* 输出:["abc","acb","bac","bca","cab","cba"]
* 方法一:通过递归的去查找每一个位置的字符可能出现情况。比如说现在要找index下标位置的字符,那么体现在代码中就是交换index以及index位置之后的那些字符即可。
*/
import java.util.*;
public class Solution_38_permutation {
private String change(char[] a) {
StringBuilder res = new StringBuilder();
for (char value : a) {
res.append(value);
}
return res.toString();
}
private void solve(ArrayList<String> ans, char[] a, int index, int length) {
if (index == length - 1) {
String res = change(a);
ans.add(res);
} else {
// 就说明现在要去确定index位置的字符
for (int i = index; i < length; i++) {
char temp = a[i];
a[i] = a[index];
a[index] = temp;
// 当前index位置的字符已经通过交换找到了,那么就递归去找下一个位置的字符
solve(ans, a, index + 1, length);
// 其实就是去为了消除当前层去递归的时候的进行交换字符的影响,如果不消除的话,那么就会造成原index位置的字符发生变化
temp = a[i];
a[i] = a[index];
a[index] = temp;
}
}
}
public ArrayList<String> Permutation(String str) {
char[] a = str.toCharArray();
ArrayList<String> ans = new ArrayList<>();
solve(ans, a, 0, str.length());
ans = new ArrayList<String>(new HashSet<String>(ans)); // 去重操作
Collections.sort(ans); // 字典排序 -> ans.sort(null);
return ans;
}
}
* 多数元素
- 数组中出现次数超过一半的数字
package basic_class_66;
/**
* 多数元素
* 数组中出现次数超过一半的数字
*
* 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
* <p>
* 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
* 示例 1:
* <p>
* 输入: [3,2,3]
* 输出: 3
* 示例 2:
* <p>
* 输入: [2,2,1,1,1,2,2]
* 输出: 2
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/majority-element
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
* <p>
* <p>
* 思路:
* map来保存每一个数字出现的次数
* target:存储出现次数最多的那个数字
* sum 存储出现次数最多的哪个数字出现的次数
* target和sum是一一对应的
*
* 遍历数组,每遍历一个数字,就记录它出现的次数
* <p>
* map.put(x,map.getOrDefault(x,0)+1);解释:
* 从map中取出key值对应的value;
* 如果没有key对应的value,就初始化成0,然后再加1,
* 如果有key对应的value值,即x,则直接取出x
*
*
* 方法一 使用map总结:
* 1、通过定义一个map去存储每个数字出现的次数
* 然后去遍历这个数组,在遍历数组中每一个位置的数字的时候,就去更新当前位置的数字出现的次数。更新完之后,
* 如果当前位置的数字出现的次数是要大于我之前所统计的出现次数最多的那个数字出现的次数,就去更新
* 更新target值(存储出现次数最多的那个数字),同时更新sum(存储出现次数最多的哪个数字出现的次数)
*
* 2、 当遍历数组这个for循环结束后,我就统计出了出现次数最多的数字以及这个数字出现的次数
*
* 3、然后就去判断这个数字出现的次数是否大于数组长度的一半,如果大于,就返回这个数字。
* 如果小于,就返回0即可。说明不存在这样的数字。
*
* 缺点:牺牲了空间复杂度,O(n)
*
*
*/
import java.util.HashMap;
import java.util.*;
public class Solution_39_majorityElement {
public int majorityElement(int[] array) {
Map<Integer, Integer> map = new HashMap<>();//存储每个数字出现的次数
int target = 0;//存储出现次数最多的那个数字
int sum = 0;//存储出现次数最多的那个数字出现的次数
for (int x : array) {
map.put(x, map.getOrDefault(x, 0) + 1);//更新当前位置出现的次数
/*
* 如果这个条件成立,则说明当前位置出现的次数是要大于我之前所统计的那个数字出现的次数
* */
if (sum < map.get(x)) {
//说明当前位置的数字出现的次数比之前统计的那个target数字出现的次数多
sum = map.get(x);//更新sum值
target = x;//更新target
}
//这时候已经已经统计出了出现次数最多的数字以及这个数字出现的次数
if (sum > array.length / 2) {//如果出现的次数大于多半,就返回这个数字
return target;
}
}
return 0;
}
}
最小的k个数
package basic_class_66;
/**
* 剑指 Offer 40. 最小的k个数 考察点:堆
* 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
*
* 示例 1:
*
* 输入:arr = [3,2,1], k = 2
* 输出:[1,2] 或者 [2,1]
* 示例 2:
*
* 输入:arr = [0,1,2,1], k = 1
* 输出:[0]
*
* 方法1、排序,取出最小的k个数
*
* 方法2、优先队列来保存,从头部取出k个数字
*
* 方法1,2可以说出来
*
* 方法3:推荐:使用堆
*
* 实现方法:
* 先把k个元素构建成一个堆
* 采用小顶堆维护:缺点:1、如果当前数大于堆顶,则不确定是否需要加入堆;2、如果小于堆顶,则调整起来很麻烦。
*
* 所以采用大顶堆
* 1、如果遍历剩余len-k个元素,如果当前位置数字>大顶堆的堆顶,则当前数字肯定不属于最小的k个数字。
* 2、如果当前位置元素小于大顶堆的堆顶,则当前数字可能属于最小的k个数字。所以用当前数字替换掉堆顶元素。
* 维护起来只需要一个函数的调用。
*
* 这道题实质:维护k个节点的大顶堆
* 需要了解:k个大顶堆如何创建
*
* 公式:非叶子节点下标=节点个数/2 - 1
* 堆的维护,自底向上(避免不必要的比较),从非叶子节点开始维护
*
* 当前节点所处位置:大于孩子节点,小于父节点
*
*/
import java.util.*;
public class Solution_40_getLeastNumbers_K {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] array, int k) {
if (k > array.length || k == 0) {//返回一个空对象,比如有5个数字,返回最小的6个数字
return new ArrayList<>();
}
// 创建堆,先用k个节点构建一个堆
int[] a = new int[k]; // 用数组去模拟k个节点的堆结构
System.arraycopy(array, 0, a, 0, k); // 初始化堆中元素,arraycopy()意思:从array数组里面第0个位置,复制到a数字,从a数组第0个位置开始,复制k个元素
// 下面就开始维护堆使其成为大顶堆 - > 堆的初始化 从非叶子节点去维护
for (int i = k / 2 - 1; i >= 0; i--) {//k / 2 - 1;-》非叶子节点下标公式
// i -> i其实就是我们所要去维护堆的节点下标
initiate(i, a, k);
}
// 去遍历剩余的len - k个节点
for (int i = k; i < array.length; i++) {
if (array[i] < a[0]) {//当前位置的值小于大顶堆堆顶的位置的值
a[0] = array[i];//让堆顶位置的值等于当前位置的值
initiate(0, a, k);//维护大顶堆
}
}
// 将大顶堆中的节点元素进行升序操作(因为大顶堆并不是一个升序)
for (int i = a.length - 1; i > 0; i--) {
// 分为两个过程, 第一步交换,第二步固定(固定的操作其实是通过控制堆的节点个数去实现的, i--)
// 相当于把最大值放到数组的末尾位置
int temp = a[i];
a[i] = a[0];
a[0] = temp;
initiate(0, a, i);
}
// 返回
ArrayList<Integer> ans = new ArrayList<>();
for (int x : a) {
ans.add(x);
}
return ans;
}
/**
* 初始化堆的函数,其实就是维护每一个节点的位置的函数
* @param index 维护当前堆的下标
* @param a 数组->堆
* @param length 堆的节点个数
*/
private void initiate(int index, int[] a, int length) {
int temp = a[index]; // 先去保存当前位置的值
for (int k = index * 2 + 1; k < length; k = k * 2 + 1) {//当前节点初始化时候,初始值为当前节点的左孩子;k = k * 2 + 1 相当于把当前节点移到它的左右孩子位置
if ((k + 1) < length && a[k + 1] > a[k]) {// 取出当前位置的左右孩子中节点值最大的节点,a[k+1]即为右孩子,a[k]为左孩子
k++;
}
if (a[k] > temp) {//左孩子大于当前值,如果temp在第三层,则a[k]就在第四层,相当于它的左或右孩子
a[index] = a[k];
index = k; // 更新index的值,index -> 代表的是temp数字最终在堆中位置,当k = k * 2 + 1执行后,index和k的关系其实就是父亲节点和孩子节点的关系。
} else {//当前元素的值大于自己的左孩子和右孩子的情况
break; // 由于我们是从下往上去维护的,所以说我们就没有往下更新的必要了
}
}
a[index] = temp; // index所在的位置进行更新就行了
}
}
数据流中的中位数
package basic_class_66;
/**
* 剑指 Offer 41. 数据流中的中位数
* 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
*
* 例如,
*
* [2,3,4] 的中位数是 3
*
* [2,3] 的中位数是 (2 + 3) / 2 = 2.5
*
* 设计一个支持以下两种操作的数据结构:
*
* void addNum(int num) - 从数据流中添加一个整数到数据结构中。
* double findMedian() - 返回目前所有元素的中位数。
* 示例 1:
*
* 输入:
* ["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
* [[],[1],[2],[],[3],[]]
* 输出:[null,null,null,1.50000,null,2.00000]
*
* 方法一:通过大顶堆和小顶堆来去维护当前数据流中的中位数
*/
import java.util.*;
public class Solution_41_GetMedian {
private PriorityQueue<Integer> queue1 = new PriorityQueue<>(((o1, o2) -> (o2 - o1))); // 中位数的左区间 > 大顶堆
private PriorityQueue<Integer> queue2 = new PriorityQueue<>(); // 中位数的右区间 > 小顶堆
private int sum = 0; // 数据流中个数
public void Insert(Integer num) {
if (sum % 2 == 0) {
// 当两个堆的元素个数一样的时候,此时新增一个元素,放入大顶堆(左区间)
queue1.add(num);
} else {
queue2.add(num);
}
if(!queue2.isEmpty() && queue1.peek() > queue2.peek()) {
assert !queue1.isEmpty();
int temp1 = queue1.poll();
int temp2 = queue2.poll();
queue1.add(temp2);
queue2.add(temp1);
}
sum++;
}
public Double GetMedian() {
if (sum % 2 == 1) {
return (double) queue1.peek();
} else {
return (queue1.peek() + queue2.peek()) / 2.0;
}
}
}
连续子数组的最大和
package basic_class_66;
/**
* 剑指 Offer 42. 连续子数组的最大和 方法2 推荐 时间复杂度O(n)
* 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
*
* 要求时间复杂度为O(n)。
* 示例1:
*
* 输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
* 输出: 6
* 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*
* 解法:
* 基本思想:对于一个最大的连续子序列的最大和来说;
* 用一个sum变量去统计某一段连续子序列的和,如果当前位置的数加上sum是大于0的,则加上后面的数字有可能比当前的和大,所以继续加上后面数字。
* 如果当前统计的sum加上当前位置的值是小于0的,则之前统计的那段子序列就没必要再统计下去了。把之前统计的子序列和sum置为0,从当前位置重新统计
*
* 实现:
* 通过定义一个统计某一个连续子序列的和的变量sum,如果当前位置加上sum之后小于0,
* 那么就将当前的sum变量变为0;
* 反之就继续遍历下一个数字。
*
*
*/
public class Solution_42_maxSubArray_method02 {
public int FindGreatestSumOfSubArray(int[] array) {
int sum = 0;
int Max = array[0];
for (int i = 0; i < array.length; i++) {
// 这几行代码的过程就是:通过sum变量去统计当前连续子序列的和,统计完之后,更新Max的值,最后判断是否更新sum的值
sum += array[i];
Max = Math.max(Max, sum);//更新Max
if (sum < 0) {//如果当前和小于0,置为0
sum = 0;
}
}
return Max;
}
}
1出现的次数
package basic_class_66;
/**
* 剑指 Offer 43. 1~n整数中1出现的次数
* 输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
*
* 例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
*
*
*
* 示例 1:
*
* 输入:n = 12
* 输出:5
* 示例 2:
*
* 输入:n = 13
* 输出:6
*
* 解法2: log(n),递归 145
* 45
* 5
* 每次舍弃最高位数字
*
* 例如:n=145
* 最高位为1,有多少情况,即百位是1情况:有100到145共46种情况
* 除了最高位,其他位是1的情况有多少种。
* 145的十位或者个位,即45中有一个是1,则另外一位有10种可能(从0到9)
* 所以145的后两位有1的情况共有2*10种
*
* 所以145中非百位中1出现的可能共有2*10*1=20种 其中 比如171>145
* 划分为 1-145
* 1-45 和46-145;对于171=》71是在46-145,
* 虽然说171>145,但是也就是说当组合的当前数字如果大于n,那么这个数字它对应的是
* 去掉当前组合数字的首位之后的那个数字,才是所要找的。
*
*
* 通过对1-n的分区间讨论(递归的过程),在求1-n的时候,分为两个区间,
* 第一个区间是1-b(b是n去掉首部数字之后的数字),
* 第二个区间是(b+1, n)。
* 对于每个区间的计算时,分为了两种情况,
* 第一种情况是当前n的首部数字是1,
* 第二种情况是除了首位的其他位是1。主要是这两中情乱的讨论。
*/
public class Solution_43_countDigitOne_method02 {
public int NumberOf1Between1AndN_Solution(int n) {
if (n == 0) {
return 0;
}
String str = "" + n;//把n转换为字符串
int len = str.length();//字符串长度
// 递归的终止条件
if (len == 1) {//如果是个位数,只有数字1才是等于1的
return 1;
}//分两种情况:最高位为1和其他位位1
int res = (int) Math.pow(10, len - 1); // 是获取当前n的幂级,比如n=245,res就是100;比n=15,res就是10
// int firstNumber = str.charAt(0) - '0';//获取当前n的第一个数字
int firstNumber = n / res;//获取当前n的第一个数字,比如n=245,res=100,则2就是245的第一个位置数字
// 分类讨论
/**
* 一、首位为1情况
* 第一种情况:第一个位置是1,那么就等于去掉首位数字之后的数字,然后在加1
* 比如n=145,res=100,n%res=145%100=45;45+1=46
* 第二种情况:第一个位置不是1时候,比如245,它就等于当前n的幂级
* 比如245,首位是1的情况有100种,(100~199)
*/
int firstBit = firstNumber == 1 ? (n % res) + 1 : res;
/**
* 二、其他位为1的情况:
* 首先看剩余有多少位,即len-1
* firstNumber 首位
* 245: 45~245=>
*
* NumberOf1Between1AndN_Solution(n % res)相当于145中判断45中包含多少个1的情况
*/
int otherBit = (len - 1) * firstNumber * res / 10; //(len - 1)的意思就是剩余位的个数(C(len-1, 1) -> 从剩余的len-1位中选取一位来作为1),res/10的意思就是剩余的len-2位可能出现的情况
return firstBit + otherBit + NumberOf1Between1AndN_Solution(n % res);
}
}
数组排成最小的数
package basic_class_66;
/**
* 剑指 Offer 45. 把数组排成最小的数
* 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
*
*
*
* 示例 1:
*
* 输入: [10,2]
* 输出: "102"
* 示例 2:
*
* 输入: [3,30,34,5,9]
* 输出: "3033459"
*
* 解法:
* 方法一:通过去设置一种比较优先级排序即可,优先级为:将比较的两个元素拼接的两种结果去比较大小,然后由他们的大小关系去比较所拼接元素的优先级。
*
* 例如: 32 和321
* 拼接结果有: 32321和32132,很明显321优先排在32前面
*/
import java.util.*;
public class Solution_45_minNumber {
public String PrintMinNumber(int [] numbers) {
ArrayList<String> list = new ArrayList<>();//把numbers存到list里面,去遍历list
//numbers添加到list
for (int x : numbers) {
list.add(x + "");
}
// 排序
//通过01和02两种拼接结果,分别去判断两种拼接结果的大小关系
// 写法1
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 下面的排序规则是核心
String a1 = o1 + o2;//第一种拼接结果
String a2 = o2 + o1;//第二种拼接结果
return a1.compareTo(a2);//比较拼接结果
}
});
// 写法2 Lambda 表达式简写
// list.sort((o1, o2) -> {
// // 下面的排序规则是核心
// String a1 = o1 + o2;//第一种拼接结果
// String a2 = o2 + o1;//第二种拼接结果
// return a1.compareTo(a2);//比较拼接结果
// });
StringBuilder ans = new StringBuilder();
// 遍历list对象
for (String x : list) {
ans.append(x);
}
return ans.toString();
}
}
丑数
package basic_class_66;
/**
* 剑指 Offer 49. 丑数
* 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
* <p>
* 2 3 5 是丑数
* <p>
* 1 2 3 4 6 8 9 10... 都是丑数
* <p>
* 从小到大的顺序的第N个丑数,所以按照从小到大的顺序取丑数
* 每次取的时候,都是取当前层的最小值
* 关键:
* 通过三个变量来维护三个队列
*/
public class Solution_49_GetUglyNumber_Solution {
public int GetUglyNumber_Solution(int n) {
if (n == 0) {
return 0;
}
int[] arr = new int[n];
arr[0] = 1;
int index1 = 0;//遍历丑数*2的队列的下标
int index2 = 0;//遍历丑数*3的队列的下标
int index3 = 0;//遍历丑数*5的队列的下标
// 推出每一个丑数,从1开始推
for (int i = 1; i < n; i++) {//因为要升序,所以从当前层的三个数中取出最小的一个放到a[i]的位置
arr[i] = Math.min(Math.min(arr[index1] * 2, arr[index2] * 3), arr[index3] * 5);
/**
* 因为上面从当前层的三个数中取出最小的一个放到a[i]的位置,要么取第一个队列,要么第二个队列,要么第三个队列,
* 取出某一个队列的值之后,要对下标进行加1操作
* 即如果说当前位置的丑数已经放到了第i个位置,则下次就不再放到第i个位置
*/
// 根据放在第i个位置上的数字更新遍历三个队列的下标
if (arr[i] == arr[index1] * 2) {
index1++;
}
if (arr[i] == arr[index2] * 3) {
index2++;
}
if (arr[i] == arr[index3] * 5) {
index3++;
}
}
return arr[n - 1];
}
}
第一个只出现一次的字符
package basic_class_66;
import java.util.HashMap;
import java.util.Map;
/**
* 剑指 Offer 50. 第一个只出现一次的字符
* 字符流中第一个不重复的字符
*
* 题目描述
* 请实现一个函数用来找出字符流中第一个只出现一次的字符。
* 例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。
* 当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
* 输出描述:
* 如果当前字符流没有存在出现一次的字符,返回#字符。
*
* 思路:
* 找出字符流中第一个只出现一次的字符
* 字符流通过insert函数往字符流中添加字符
*
* 1、用map来保存当前字符流中每一个字符出现的次数
* 2、遍历当前字符,同时借助map来判断当前位置的字符出现的次数
* 是否为1,如果是1,直接返回当前字符;
* 如果没有出现次数为1的字符,返回false
*
*
*/
public class Solution_50_FirstAppearingOnce {
private Map<Character,Integer> map = new HashMap<>();//保存每个字符出现的次数
private StringBuilder str = new StringBuilder();//保存字符流
private int index = 0;//用来保存字符只出现一次的第一个位置
/**
* 往字符流中添加字符
* @param ch
*/
public void Insert(char ch){
str.append(ch);//添加字符
/**
* 如果ch没有出现过,就取0
* 如果出现过,就取ch出现的次数再加上1
*/
map.put(ch,map.getOrDefault(ch,0)+1);
}
/**
* 返回第一次只出现一次的位置
* 但是有可能在某一次添加过程中,我添加的字符和index保存的字符一样,
* 那么当前的index就是不符合条件的,
* 所以调用FirstAppearingOnce()函数的时候,
* 需要通过while循环去判断一下,当前的index是否为满足题意的位置
* 如果不满足就加加,加加之后,到达某一个位置的时候,发现当前位置出现的字符只出现1次
* 直接返回当前字符即可。
* 如果把当前字符流遍历完还没有发现只出现一次的字符,则返回#即可
*
* @return
*/
public char FirstAppearingOnce(){
while (index<str.length()){
if (map.get(str.charAt(index))==1){
return str.charAt(index);
}
index++;//如果index不在当前位置,就加加
}
return '#';
}
}
方法二
package basic_class_66;
import java.util.*;
/**
* 剑指 Offer 50. 第一个只出现一次的字符
* 第一个只出现一次的字符
*
* 题目描述
* 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
*
* 考察:哈希表map操作
* 思路:
* 1、通过第一次遍历,统计这个字符串中每一个字符出现的次数,把这个字符和出现的次数加入到map中
* 2、第二次遍历,判断每个字符在这个字符串中出现的次数,如果等于1,返回当前字符,
* 如果遍历完都没有发现出现次数等于1,返回-1
*/
/**
* 遍历每个字符的时候,把当前字符出现的次数放入到map当中
* key即当前字符
* value:分两种情况:
* 1、当前字符如果出现过,获取当前字符出现的次数,即直接取key对应的value即可;
* 2、当前字符如果没有出现,就为0
* 加1,代表把当前i位置的字符给统计上
*/
public class Solution_50_FirstNotRepeatingChar {
public int FirstNotRepeatingChar(String str) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0; i < str.length(); i++) {
map.put(str.charAt(i), map.getOrDefault(str.charAt(i), 0) + 1);
}
for(int i=0;i<str.length();i++){
if(map.get(str.charAt(i))==1){
return i;
}
}
return -1;
}
}
数组中的逆序对
package basic_class_66;
/**
* 剑指 Offer 51. 数组中的逆序对
* 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
*
*
*
* 示例 1:
*
* 输入: [7,5,6,4]
* 输出: 5
*
* 考察点:归并排序
*
* 方法一:通过对并排序的合并过程去统计逆序对的个数,那么统计逆序对的前提就是在归并排序的合并过程中,合并的两个子序列都是有序的,所以说才可以用归并排序去统计逆序对的个数
*
* 合并的时候,其实是通过两个下标去遍历两个子序列,左边和右边的子序列都是有序的,
* 比如i指向左边的序列中的数字,j指向右边序列中的数字,
* 如果i>j即 j所指向的数字小于i当前所指向的数字,则i当前所指向的数字以及i之后的数字都是可以跟当前j所指的数字组成逆序对。
*
* 相当于在归并排序的基础上,加上一行统计逆序对的代码
*
*/
public class Solution_51_reversePairs {
private long sum; // 用来去统计逆序对的个数,如果是降序,逆序对就是1到n的等差数列的累加和,是超过了int范围,所以用long
public int InversePairs(int [] array) {
sum = 0;
int l = 0;//分解区间的起点
int r = array.length - 1;//分解区间的终点
divide(l ,r, array);//分解函数
return (int) (sum % 1000000007);
}
private void divide(int l, int r, int[] array) {
if (l != r) {//当前分解的长度不等于1 比如53
int mid = (l + r) >> 1;//中点位置
divide(l, mid, array);//划分左区间 5
divide(mid + 1, r, array);//划分右区间 3
merge(l, r, mid, array);//合并两个子序列[l,r] 3 5
}
}
// 合并
private void merge(int l, int r, int mid, int[] array) {
int i = l; // 左区间的起点
int j = mid + 1; // 右区间的起点
int[] temp = new int[r - l + 1];//保存临时合并的结果
int index = 0;
while (i <= mid && j <= r) {//因为传入的是r = array.length - 1,所以数组中实际下标的变量值就是r,所以<=
if (array[i] > array[j]) {//当前左区间的数字大于当前右区间数字
temp[index++] = array[j++];//先把小的数字放入临时数组,放入之后j++
// 统计逆序对; mid - i 即左区间的终点位置减去当前位置,再加1就是当前起点位置到mid的终点位置数字的个数
sum += mid - i + 1; // 这一行是核心,去统计逆序对个数,统计的基础是在归并排序的合并过程中,合并的两个子序列都是有序的
} else {//就是左边的小于右边,那么不满足逆序对了
temp[index++] = array[i++];//把当前i所指向的数字移到临时数组里面
}
}
//i <= mid && j <= r不同时成立的情况:
while (i <= mid) {//说明左区间还有数字
temp[index++] = array[i++];//将左区间剩余的数字放到临时数组里面
}
while (j <= r) {//说明右区间还有数字
temp[index++] = array[j++];//将右区间剩余的数字放到临时数组里面
}
// 这时候把[l,r]已经排好序了,放入到array数组中
index = 0;
for (int k = l; k <= r; k++) {
array[k] = temp[index++];
}
}
}
两个链表的第一个公共节点
package basic_class_66;
/**
* 剑指 Offer 52. 两个链表的第一个公共节点
*
* 输入两个链表,找出它们的第一个公共节点。
* ###方法一:通过栈去模拟从链表的尾部往前遍历两个链表的重合的部分,找到最左侧重合点即可
* 出现不一样的情况的节点的上一个即为第一个公共节点
*
* 执行用时:
* 3 ms, 在所有 Java 提交中击败了13.21%
* 内存消耗:41.9 MB 在所有 Java 提交中击败了12.52% 的用户
*
*/
import java.util.*;
public class Solution_52_FindFirstCommonNode_method01 {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
// 通过遍历 把两个链表节点 放入栈1、栈2
while (pHead1 != null) {
stack1.add(pHead1);//放入栈
pHead1 = pHead1.next;//更新节点的值
}
while (pHead2 != null) {
stack2.add(pHead2);
pHead2 = pHead2.next;
}
ListNode ans = null;//返回结果对象
// 遍历两个栈
while (!stack1.isEmpty() && !stack2.isEmpty()) {//两个栈不为空
if(stack1.peek().val == stack2.peek().val) {//如果它两个栈顶元素一样,说明处于两个两个栈栈顶处于重合部分
//因为是从链表尾部往链表的头部找的,所以只有在两个栈的栈顶元素相同的时候,才会去更新ans,
ans = stack1.peek();//用ans记录重合部分的头,也就是第一个共同的节点
//因为栈顶元素比较过了,所以对栈1和栈2的栈顶元素删除即可
stack1.pop();
stack2.pop();
} else {//如果不一样,就没有必要再往后找了
break;
}
}
return ans;
}
}
方法二:推荐
package basic_class_66;
/**
* 推荐
* 方法二:先去判断两个链表的长度,移动其中一个链表的头节点,使其两个链表的长度一样,最后从两个链表的头部开始遍历,找到第一个重合点即可。
*
* 基于两个链表的长度来判断:
* 先去判断两个链表的长度,移动其中一个链表的头节点,使其两个链表的长度一样
* 然后再去判断两个链表,遍历过程中判断所指向的节点值是否一样,如果一样就是要找的第一个公共节点
* 如果不一样,继续同时往后移动
*
*/
public class Solution_52_FindFirstCommonNode_method02 {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//两个链表长度
int len1 = 0;
int len2 = 0;
//两个移动节点
ListNode removeNode1 = pHead1;
ListNode removeNode2 = pHead2;
//遍历两个链表,统计链表长度
while (removeNode1 != null) {
len1++;
removeNode1 = removeNode1.next;
}
while (removeNode2 != null) {
len2++;
removeNode2 = removeNode2.next;
}
// 比较两个链表的长度length是否一样,如果一样,同时遍历;如果不一样,去移动 phead1,或者pHead2
if (len1 > len2) {//第一个链表长度>第二个链表长度,移动链表1,
for (int i = 1; i <= len1 - len2; i++) {//移动的步数就是len1-len2
pHead1 = pHead1.next;//移动链表1
}
} else if (len2 > len1) {
for (int i = 1; i <= len2 - len1; i++) {
pHead2 = pHead2.next;//移动链表2
}
}//如果一样,同时开始移动
ListNode ans = null;
while (pHead1 != null) {//两个一样的
if (pHead1.val ==pHead2.val) {//如果两个节点的val一样,说明当前节点就是所要找的第一个公共的节点
ans = pHead1;
break;
}//如果不一样,两个节点同时往后移动
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
return ans;
}
}
二叉搜索树的第k大节点
package basic_class_66;
/**
* 二叉搜索树的第k大节点
* <p>
* 给定一棵二叉搜索树,请找出其中第k大的节点。
* <p>
* 思路:
* 在找二叉搜索树的第k小的节点,采用中序遍历,第k个节点就是第k小的节点。
* 如何求第k大的节点:??
* <p>
* 在中序遍历基础上,把left和right进行交换,
* 先遍历当前节点的右孩子,再遍历当前节点,再遍历当前节点的左孩子
* 这样遍历得到的是一个单调递减的序列。
*/
public class Solution_54_KthLargestNode {
private TreeNode ans;
private int index;
public int KthLarget(TreeNode root, int k) {
index = 1;
ans = null;
if (k != 0 && root != null) {
solve(root, k);
}
// return ans;
return ans.val;//因为要返回int类型
}
private void solve(TreeNode node, int k) {
if (ans == null) {
if (node.right != null) {
solve(node.right, k);
}
if (index == k) {
ans = node;
}
if (node.left != null) {
solve(node.left, k);
}
}
}
}
二叉树的深度
package basic_class_66;
/**
* 55 二叉树的深度
*
* 题目描述
* 输入一棵二叉树,求该树的深度。
* 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
* <p>
* 思路:
* 深度:从根结点到叶结点依次经过的结点个数,并且路径是最长的
* 二叉树的遍历(统计根节点到叶子节点的节点个数)
* 先递归左孩子,看左孩子到叶子节点的个数
* 再递归右孩子到叶子节点的个数
* <p>
* 当遍历到二叉树的当前某一个节点时候,(非空节点)
* 去看下当前节点的左孩子距离叶子节点有多少个节点
* 再看下当前节点的右孩子距离叶子节点有多少个节点
* 在从这两个递归返回的值中选取最大值,就是当前节点距离叶子节点最长的长度
* <p>
* 递归终止条件:null 叶子节点
*/
public class Solution_55_1_TreeDepth {
public int TreeDepth(TreeNode node) {
if (node == null) {
return 0;
}
return Math.max(TreeDepth(node.left), TreeDepth(node.right))+1;//加1就是当前node对路径产生的影响
}
}
和为s的连续正数序列
package basic_class_66;
/**
* 剑指 Offer 57 - II. 和为s的连续正数序列
*
* 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
*
* 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
import java.util.*;
public class Solution_57_findContinuousSequence {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
// 定义两个指针:代表两个位置,代表窗口最左侧和最右侧的数字
int l = 1;
int r = 2;
ArrayList<Integer> list = new ArrayList<>();//存储每次找到的和为S的正数序列,list是动态变化的
// 对list初始化,list和l,r是一一对应的
list.add(1);
list.add(2);
/**
* 如果l>s/2,则[l,r]区间上的数字和大于s
*/
while (l < (sum + 1) / 2) {//当区间的左端点位于当前sum一半往左的时候,进行迭代
int tempSum = cul(l, r);//保存当前l到r区间的值和
while (tempSum > sum) {//当前区间数字和大于sum
tempSum -= l;//相当于把当前区间左端点的数字删除掉
l++;//区间左边端点右移
list.remove(0);//将list当中第0个位置数字清除掉
}
if (tempSum == sum) {//找到了区间
ans.add(new ArrayList<>(list));//把当前list值重新生成一个对象,再添加到ans中
// 减值,对应remove
l++;//找到之后,让区间的左端点向右移动,同时删除掉当前左端点第0个位置数字
list.remove(0);
}//tempSum小于sum,让右端点向右移动
// 添值,对应add
r++;//右端点向右移动
list.add(r);//将当前右端点值添加到list当中
}
return ans;
}
// 等差数列求和,(a1+an)*项数/2,因为题目是连续正数序列。r - l + 1代表区间内的数字个数
private int cul(int l, int r) {
return (l + r) * (r - l + 1) / 2;
}
}
方法二
package basic_class_66;
/**
* 剑指 Offer 57 - II. 和为s的连续正数序列
*
* 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
*
* 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
*
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
import java.util.*;
public class Solution_57_findContinuousSequence_method01_leetcode {
public int[][] findContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
// 定义两个指针:代表两个位置,代表窗口最左侧和最右侧的数字
int l = 1;
int r = 2;
ArrayList<Integer> list = new ArrayList<>();//存储每次找到的和为S的正数序列,list是动态变化的
// 对list初始化,list和l,r是一一对应的
list.add(1);
list.add(2);
/**
* 如果l>s/2,则[l,r]区间上的数字和大于s
*/
while (l < (sum + 1) / 2) {//当区间的左端点位于当前sum一半往左的时候,进行迭代
int tempSum = cul(l, r);//保存当前l到r区间的值和
while (tempSum > sum) {//当前区间数字和大于sum
tempSum -= l;//相当于把当前区间左端点的数字删除掉
l++;//区间左边端点右移
list.remove(0);//将list当中第0个位置数字清除掉
}
if (tempSum == sum) {//找到了区间
ans.add(new ArrayList<>(list));//把当前list值重新生成一个对象,再添加到ans中
// 减值,对应remove
l++;//找到之后,让区间的左端点向右移动,同时删除掉当前左端点第0个位置数字
list.remove(0);
}//tempSum小于sum,让右端点向右移动
// 添值,对应add
r++;//右端点向右移动
list.add(r);//将当前右端点值添加到list当中
}
// ArrayList-> 数组
int[][] a = new int[ans.size()][];//长度为list中元素长度
for (int i=0;i<ans.size();i++){
// 第i个满足条件的序列的长度
a[i] =new int[ans.get(i).size()];
for (int j = 0; j <ans.get(i).size(); j++) {
a[i][j] = ans.get(i).get(j);
}
}
return a;
}
// 等差数列求和,(a1+an)*项数/2,因为题目是连续正数序列。r - l + 1代表区间内的数字个数
private int cul(int l, int r) {
return (l + r) * (r - l + 1) / 2;
}
}
和为s的两个数字
package basic_class_66;
import java.util.HashMap;
import java.util.Map;
/**
* 剑指 Offer 57. 和为s的两个数字
* 两数之和 (无序数组)
* 考点:哈希表
*
*
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
*/
public class Solution_57_twoSum_disorder {
public static int[] twoSum(int[] nums, int target) {
//声明一个哈希表,即map
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//从左往右,依次遍历nums
for (int i = 0; i < nums.length; i++) {
int temp = target-nums[i];//临时元素等于 target和当前遍历元素的差值
if (map.containsKey(temp)){//如果差值在哈希表里
return new int[]{map.get(temp), i};//将差值所在哈希表里面的位置map.get(temp)和当前元素的下标返回即可。
}
map.put(nums[i], i);//如果差值不在哈希表里,那么将当前遍历的这个元素和他的下标放到哈希表里面去
}//如果遍历完整个哈希表都没有找到,则直接返回{-1,-1}即可
return new int[]{-1,-1};
}
public static void main(String[] args) {
int[]nums = {2, 7, 11, 15};
int target = 9;
int[] array = twoSum(nums,target);
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
}
有序的数组
package basic_class_66;
/**
* 和为s的两个数(递增排序的数组和一个数字S)
* * 题目描述
* * 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的(-x*x + s*x)。
* * 抛物线,开口向下,对于数字和为s的两个位置,对称轴左侧单调递增,让x尽可能的小,则他们对应的乘积越小。则,例如
* * s =12
* * 2 10
* * 4 8
* * 两对,应该去2 10这一对。
* *
* *
* *
* * 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
* *
* *
* *
* * 示例 1:
* *
* * 输入:nums = [2,7,11,15], target = 9
* * 输出:[2,7] 或者 [7,2]
* * 示例 2:
* *
* * 输入:nums = [10,26,30,31,47,60], target = 40
* * 输出:[10,30] 或者 [30,10]
*/
public class Solution_57_twoSum_order_twoPointer {
public int[] twoSum(int[]nums,int target){
int[] arr = new int[2];
int l = 0;
int r = nums.length-1;
while (l<r){
if (nums[l]+nums[r]==target){
arr[0] = nums[l];
arr[1] = nums[r];
break;
}else if(nums[l]+nums[r]<target){
l++;
}else {
r--;
}
}
return arr;
}
}
翻转单词顺序列/或者叫翻转字符串
package basic_class_66;
/*
* 翻转单词顺序列/或者叫翻转字符串
*
* 题目描述
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。
* 同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,
* “student. a am I”。
*
* 后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是
* “I am a student.”。
* Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
*
*
* 思路:
* 第1个单词翻转之后,应该放在最后
*
* 翻转两次:
* 第一次对整个单词序列翻转: 确定每一个单词所在的位置
* 第二次翻转:不是翻转整个单词序列,而是翻转这个序列中的每一个单词(消除第一次翻转对每个单词的影响)
*
* */
public class Solution_58_ReverseSentence {
public String ReverseSentence(String str) {
/**
* 第一次翻转:对整个序列
* 通过传入一个String类的对象str,放入到StringBuffer类的构造函数中,
* 就是创建一个StringBuffer类的对象,
* StringBuilder类中有 一个reverse()函数,对这个字符串进行翻转,翻转之后转化为String
*/
String flipStr = new StringBuilder(str).reverse().toString();//第一次翻转
// 第二次翻转:以单词为单位,即以空格进行分割,遍历每一个单词(也可以通过函数split())
StringBuilder res = new StringBuilder();//用来保存遍历到的每一个单词
StringBuilder ans = new StringBuilder();//保存最终返回的str的结果
for (int i = 0; i < flipStr.length(); i++) {
if (flipStr.charAt(i) == ' ') {//如果第i个字符等于空格,
// 第二次翻转
ans.append(res.reverse().toString()).append(" ");//单词与单词之间的空格,所以单词翻转后加一个空格
// 现在翻转的单词已经保存到ans中了,所以需要将ans置为空,来保存下一个单词
res = new StringBuilder();
} else {//如果第i个字符不等于空,说明当前这个单词还没有遍历结束,就把当前字符加入到res中
res.append(flipStr.charAt(i));
}
}
/*
当遍历到最后一个字符,res = s ,通过i < flipStr.length();跳出for循环,不再执行if条件进行第二次翻转。
所以需要在for循环外边
将最后的那个单词翻转结果保存到ans当中
*/
ans.append(res.reverse().toString());//最后的那个单词翻转结果保存到ans当中
return ans.toString();
}
}
leetcode
package basic_class_66;
/**
* 翻转单词顺序
* 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
* 为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
* <p>
* 示例 1:
* <p>
* 输入: "the sky is blue"
* 输出: "blue is sky the"
* <p>
* 示例 2:
* <p>
* 输入: " hello world! "
* 输出: "world! hello"
* 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
* <p>
* <p>
* 示例 3:
* <p>
* 输入: "a good example"
* 输出: "example good a"
* 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
* <p>
* <p>
* 单词序列两边的空格和单词序列中间的空格处理
*/
public class Solution_58_ReverseWords_leetcode {
public String reverseWords(String str) {
// 第一次翻转 str.trim()去掉单词两边的空格
String flipStr = new StringBuilder(str.trim()).reverse().toString();
StringBuilder res = new StringBuilder();
StringBuilder ans = new StringBuilder();
/*
* 如果两个单词之间有多个空格,则res是一定为空的,通过这样方式的删除中间多余空格
* 把多余的空格删除掉之后,我们还是需要添加一个空格,即比如:三个空格——》一个空格
* res.length()>0){//说明当前res是有字符的
* ans.length()==0,说明当前ans中还没有添加单词,那么就将当前的字符翻转之后,保存到ans当中
* 如果当前有字符,则在前面加一个空格,再将当前的字符翻转之后保存到ans
*
* 总结:
* 就是我在将翻转后的单词添加到ans的时候,
* 我先去判断ans中是否有单词,如果有单词,我就在这两个单词之间加上一个空格;
* 如果没有单词,直接放入一个单词就可以
* */
for (int i = 0; i < flipStr.length(); i++) {
if (flipStr.charAt(i) == ' ') {
if (res.length() > 0) {//说明当前res是有字符的,
ans.append(ans.length() == 0 ? res.reverse() : " " + res.reverse());
res = new StringBuilder();
}
} else {
res.append(flipStr.charAt(i));
}
}
if (res.length() > 0) {
ans.append(ans.length() == 0 ? res.reverse() : " " + res.reverse());
}
return ans.toString();
}
}
幸存者
package basic_class_66;
import java.util.LinkedList;
//链表来模拟
//超出时间限制 leetcode
public class Solution_62_LastRemaining_03 {
public static int LastRemaining(int n, int m) {
//链表来模拟
if (m == 0 || n == 0) {
return -1;
}
//否则,定义一个链表,把这些人加进去
LinkedList<Integer> list = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
list.add(i);
}
//加入链表之后,来检查该杀掉哪个人
int removeIndex = 0;//定义一个杀人的下标
while (list.size() != 1) {//当链表只剩下一个人的时候,停止杀人
removeIndex = (removeIndex + m - 1) % list.size();
list.remove(removeIndex);
}
//跳出循环,即链表只剩下一个人,返回这个人的下标
return list.get(0);
}
}