数据结构与算法
O(1)
使用递归的条件
leetcode Hot100 第70题
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
个人解析
根据题目可以知道,每次只能爬1或者2个台阶
所以可以得到 f(1) = 1; f(2) = 2;
那么首先可以确定的是,因为每次只有1或者2两个选择,那么从两个台阶开始,每增加一阶台阶
设当前为台阶数为n,那么爬1阶台阶的可能的次数就是(n-1)*1
爬两阶因为可以选择爬一阶或者两阶,所以从n-2爬会有两种可能,那就是(n-2)*2
但是又因为n-2阶爬一阶时,就会与n-1重复,所以n-2去重后只剩爬两阶的可能
所以n-2阶开始爬,可以算作n-1爬一阶的方法数+n-2
所以爬n阶的方法数就等于 爬到n-1的方法数+爬到n-2的方法数
所以得到分段公式
f(1) = 1; n = 1
f(2) = 2; n = 2
f(n) = f(n-1) + f(n-2); n > 2
代码解析
递归+动态规划
单方面使用递归,会使时间复杂度变高
public class ClimbStairs {
public static void main(String[] args) {
/**
* 创建map存储重复数据,防止多次运算拖慢速度
* 因为每次求n阶时,都会对n阶之前的结果进行运算,导致效率很低,所以使用动态规划创建map记录数据
*/
Map<Integer, Integer> map = new HashMap<>();
System.out.println(climb(45, map));
}
private static int climb(int n, Map<Integer, Integer> map){
if (n == 1){
map.put(n, 1);
return 1;
}
if (n == 2){
map.put(n, 2);
return 2;
}
int value = 0;
if (map.isEmpty()){
value = climb(n - 1, map) + climb(n - 2, map);
map.put(n, value);
}else {
value = map.get(n-1) + map.get(n - 2);
}
return value;
}
}
剑指offer 第10 I 题
题目
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
* f(n) = 0; n = 0;
* f(n) = 1; n = 1;
* f(n) = f(n - 1) + f(n - 2); n >= 2;
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
代码
/**
* @author zwj
* @description 斐波那契数列
* f(n) = 0; n = 0;
* f(n) = 1; n = 1;
* f(n) = f(n - 1) + f(n - 2); n >= 2;
* @location 剑指offer 第10 I题
* @date 2022/7/22 - 14:40
*/
public class ClimbStairsPro {
public static void main(String[] args) {
System.out.println(numberCol(45));
}
public static int numberCol(int n){
/**
* for循环解决问题
* 1.建立两个变量存放算出的数据,避免重复计算
* 2.每次计算完成后将值赋给变量
*/
int preVal = 0;
int postVal = 1;
//结果值
int value = 0;
//判断0和1
if (n == 0) return preVal;
if (n == 1) return postVal;
for (int i = 2; i <= n; i++) {
value = (preVal + postVal) % 1000000007;
//将n-1的值赋给pre,将新值赋给post
preVal = postVal;
postVal = value;
}
return value;
}
}
leetcode Hot100 第1题
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
个人解析
解析一:
暴力破解,数组循环
解析二:
使用map,因为map是数组加链表加红黑树的结构,插入数据速度快,获取数据相对于纯链表也快
每次拿数据和map中的数据进行相加比对,没有就放入map集合
代码
/**
* @author zwj
* @description 两数之和
* @location leetcode HOT100 第一题
* @date 2022/7/22 - 15:09
*/
public class TwoNumSum {
public static void main(String[] args) {
int[] num = {5, 8, 6, 9, 11, 55, 7, 21};
Stream.of(byArray(num, 76)).forEach(System.out::println);
Stream.of(byMap(num, 76)).forEach(System.out::println);
}
//数组暴力破解
public static Integer[] byArray(int[] num, int value){
for (int i = 0; i < num.length; i++) {
for (int j = 0; j < num.length; j++) {
if (num[i] + num[j] == value){
return new Integer[]{i, j};
}
}
}
return new Integer[0];
}
//Map
public static Integer[] byMap(int[] num, int value){
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < num.length; i++) {
if (map.containsKey(value - num[i])){
return new Integer[]{map.get(value - num[i]), i};
}
map.put(num[i], i);
}
return new Integer[0];
}
}
leetcode 第88题
题目
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
个人解析
解析一:
数组循环放入,然后使用sort方法进行排序
解析二:
双指针进行判断,创建新的临时数组,将小的数插入到新数组,指针后移,没插入的数组指针不移。
如果指针在某个数组移动到最后,则将另一个数组剩余数据顺序插入
时间复杂度O(m+n)
解析三:
因为数组一的长度是两个数组的长度和,所以,可以利用数组一的空间,节省临时数据占用的内存
双指针从数组尾部开始比较,从数组一的尾部开始插入
代码
/**
* @author zwj
* @description 合并有序数组
* @location leetcode 第88题
* @date 2022/7/22 - 16:20
*/
public class MergeArray {
public static void main(String[] args) {
// int[] byArray = byArray(new int[]{1, 2, 3, 0, 0, 0}, 3, new int[]{2, 3, 5}, 3);
// for (int i : byArray) {
// System.out.println(i);
// }
// int[] byASCNode = byASCNode(new int[]{1, 2, 3, 0, 0, 0}, 3, new int[]{2, 3, 5}, 3);
// for (int i : byASCNode) {
// System.out.println(i);
// }
int[] byDESCNode = byDESCNode(new int[]{1, 2, 3, 0, 0, 0}, 3, new int[]{2, 3, 5}, 3);
for (int i : byDESCNode) {
System.out.println(i);
}
}
/**
* 数组暴力破解
* @param num1 数组一
* @param m 数组一有效值
* @param num2 数组二
* @param n 数组二有效值
* @return
*/
public static int[] byArray(int[] num1, int m, int[] num2, int n){
for (int i = 0; i < num2.length; i++) {
num1[m + i] = num2[i];
}
Arrays.sort(num1);
return num1;
}
/**
* 双指针正向比较
* @param num1 数组一
* @param m 数组一有效值
* @param num2 数组二
* @param n 数组二有效值
* @return
*/
public static int[] byASCNode(int[] num1, int m, int[] num2, int n){
int[] temp = new int[m + n];
//创建指针
int first = 0;
int second = 0;
for (int i = 0; i < m + n; i++) {
if (first >= m){
temp[i] = num2[second];
second++;
}else if (second >= n){
temp[i] = num1[first];
first++;
}else if (num1[first] >= num2[second]){
temp[i] = num2[second];
second++;
}else {
temp[i] = num1[first];
first++;
}
}
for (int i = 0; i < m + n; i++) {
num1[i] = temp[i];
}
return num1;
}
/**
* 双指针反向比较 ---不创建临时数组
* @param num1 数组一
* @param m 数组一有效值
* @param num2 数组二
* @param n 数组二有效值
* @return
*/
public static int[] byDESCNode(int[] num1, int m, int[] num2, int n){
//创建指针
int first = m - 1;
int second = n - 1;
for (int i = m + n - 1; i < m + n; i--) {
if (second < 0){
break;
}else if (first < 0){
num1[i] = num2[second];
second--;
}else if (num1[first] >= num2[second]){
num1[i] = num1[first];
first--;
}else {
num1[i] = num2[second];
second--;
}
}
return num1;
}
}
leetcode 第283题
题目
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
个人解析
解析:
设置两个指针,一个指针遍历数组,另一个指针停留在0的位置,每有一个值覆盖,指向0的指针后移,最后将指向0的指针和遍历指针之间置为0
代码
/**
* @author zwj
* @description 移动零
* @location leetcode 第283题
* @date 2022/7/22 - 17:51
*/
public class MoveZero {
public static void main(String[] args) {
int[] byArray = byNode(new int[]{0, 1, 0, 3, 12});
for (int i : byArray) {
System.out.println(i);
}
}
/**
* 数组移动覆盖,使用指针
* @param nums
* @return
*/
public static int[] byNode(int[] nums){
//创建指针
int zero = 0;
int next = 0;
for (int i = 0; i < nums.length; i++, next++) {
if (nums[next] != 0) {
nums[zero] = nums[next];
zero++;
}
}
for (int i = zero; i < nums.length; i++) {
nums[i] = 0;
}
return nums;
}
}
leetcode 第448题
题目
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果.
分析
# 取反
在不需要额外空间的情况下,只能操作当前数组,时间复杂度为O(n)
因为nums[i]在在区间内,而且区间内数字从1开始,所以可以将数组中每个数字减一的下标位置取反
这样只有数组中缺失的数字减1的下标位置为正数。得到缺失的值
执行用时:5 ms, 在所有 Java 提交中击败了52.29%的用户
内存消耗:49.8 MB, 在所有 Java 提交中击败了16.93%的用户
通过测试用例:33 / 33
# 加值取模
和取反原理一样,将数组中的每个值减一位置的值加n,找下标时取模,最后小于n下标的就是缺失的
执行用时:3 ms, 在所有 Java 提交中击败了99.72%的用户
内存消耗:49.5 MB, 在所有 Java 提交中击败了40.10%的用户
通过测试用例:33 / 33
代码
/**
* @author zwj
* @description 找到数组中所有缺失的值
* @location leetcode 第448题
* @date 2022/7/25 - 9:48
*/
public class FindLoseNum {
public static void main(String[] args) {
int[] nums = {4,3,2,7,8,2,3,1};
// find(nums);
List<Integer> byMod = findByMod(nums);
for (Integer integer : byMod) {
System.out.println(integer);
}
}
/**
* 取反
* @param nums
*/
public static void find(int[] nums){
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0){
if (nums[~nums[i]] < 0){
continue;
}
nums[~nums[i]] = ~nums[~nums[i]] + 1;
}else {
if (nums[nums[i] - 1] < 0){
continue;
}
nums[nums[i] -1] = ~nums[nums[i] - 1] + 1;
}
}
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0){
list.add(i + 1);
}
}
for (Integer integer : list) {
System.out.println(integer);
}
}
public static List<Integer> findByMod(int[] nums){
List<Integer> list = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
//对每个位置上的数取模减一获取下标
int index = (nums[i] - 1) % n;
//将下标上的值加数组长度
nums[index] += n;
}
//判断小于n的值输出
for (int i = 0; i < nums.length; i++) {
if (nums[i] <= n){
list.add(i + 1);
}
}
return list;
}
}
leetcode 第21题
题目
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
分析
# 指针
准备一个结果节点,比较两个链表的每个节点的值,将小的值放入到结果节点,然后取出值的节点指针后移,直到为null
# 递归
递归链表的每个节点,进行比较,不断递归链表的next节点,然后将比较值接在next节点上,不需要创建新的链表空间
内存空间占用大
代码
/**
* @author zwj
* @description 合并有序链表
* @location leetcode 第21题
* @date 2022/7/25 - 10:46
*/
public class MergeNode {
public static void main(String[] args) {
}
public static ListNode merge(ListNode list1, ListNode list2){
//判断是否有链表为空
if (list1 == null) return list2;
if (list2 == null) return list1;
//创建结果节点
ListNode result = new ListNode(0);
//创建p作为结果节点的指针
ListNode p = result;
while (list1 != null && list2 != null){
if (list1.val < list2.val){
p.next = list1;
list1 = list1.next;
}else {
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
if (list1 == null) p.next = list2;
if (list2 == null) p.next = list1;
return result.next;
}
public static ListNode mergeByWhile(ListNode list1, ListNode list2){
if (list1 == null) return list2;
if (list2 == null) return list1;
if (list1.val < list2.val){
list1.next = mergeByWhile(list1.next, list2);
return list1;
}else {
list2.next = mergeByWhile(list1, list2.next);
return list2;
}
}
}
leetcode 第83题
题目
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表
分析
因为是有序链表,所以只需要遍历,与后一个节点比较,如果相同,直接指向后一个节点的下一个节点
# 还可以用递归
代码
/**
* @author zwj
* @description 删除有序链表中重复节点
* @location leetcode 第83题
* @date 2022/7/25 - 14:10
*/
public class DeleteDuplicates {
public static void main(String[] args) {
}
public static ListNode delete(ListNode head){
if (head == null) return null;
//创建指针节点
ListNode point = head;
while(point.next != null){
if (point.val == point.next.val){
point.next = point.next.next;
}else {
point = point.next;
}
}
return head;
}
}
leetcode 第141题 和 第142题
题目
# 141 简单
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
# 142 中等
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
分析
# 141
可以使用hash表,存储节点,如果出现相同节点就说明是环形,内存空间为O(n)
使用快慢指针,Floyd判圈算法
一个指针移动速度设为1,另一个指针速度设为2,当两个指针相遇时,就说明环形
# 142
代码
141
/**
* @author zwj
* @description 判断是否为环形链表
* @location leetcode 第141题
* @date 2022/7/25 - 14:42
*/
public class hasCircle {
public static boolean justify(ListNode head){
if (head == null) return false;
//创建指针
ListNode slowPoint = head;
ListNode fastPoint = head;
while (fastPoint.next != null && fastPoint.next.next != null){
slowPoint = slowPoint.next;
fastPoint = fastPoint.next.next;
if (slowPoint == fastPoint){
return true;
}
}
return false;
}
}
142
/**
* @author zwj
* @description 环形队列二 返回节点
* @location leetcode 第142题
* @date 2022/7/25 - 16:05
*/
public class ReturnCircleNode {
public static ListNode returnNode(ListNode head){
if (head == null) return null;
if (head.next == null) return null;
//创建指针
ListNode slowPoint = head, fastPoint = head;
ListNode pre = head;
while (fastPoint != null){
slowPoint = slowPoint.next;
if (fastPoint.next != null){
fastPoint = fastPoint.next.next;
}else {
return null;
}
if (fastPoint == slowPoint){
while (pre != slowPoint){
pre = pre.next;
slowPoint = slowPoint.next;
}
return pre;
}
}
return null;
}
}
leetcode 第160题
题目
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
图示两个链表在节点 c1 开始相交:
相交后数据长度一致。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构
分析
由题可知,如果链表相交,那么从交点到最后是一样的,所以,两个链表的关键就是交点前的长度。
设置两个指针,同时向后走,当一个指针走到头时,指针转移到另一个指针所在列表的头部,这样,另一个指针走完剩下的,这个指针就走到短链表相同的起始位置了,然后再将长链表的指针转到短链表开头,依次比对,找到交点
代码
/**
* @author zwj
* @description 相交链表
* @location leetcode 第160题
* @date 2022/7/25 - 17:16
*/
public class IntersectionNode {
public static ListNode getIntersectionNode(ListNode head1, ListNode head2){
if (head1 == null) return null;
if (head2 == null) return null;
//创建指针
ListNode s = head1;
ListNode p = head2;
while (s != p){
s = s == null ? head2 : s.next;
p = p == null ? head1 : p.next;
}
return p;
}
}
leetcode 第206题
题目
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
分析
创建一个新的链表,将第一个节点指向新的链表,将后一个节点保存,重复操作
代码
/**
* @author zwj
* @description 反转链表
* @location leetcode 第206题
* @date 2022/7/25 - 17:47
*/
public class ReverseList {
public static ListNode reverse(ListNode head){
if (head == null) return null;
//创建新链表
ListNode newList = null;
//创建当前链表指针
ListNode curr = head;
while (curr != null){
//将当前链表循环赋值给临时链表
ListNode point = curr.next;
//当前链表节点指向新链表
curr.next = newList;
//将当前链表节点重新赋值给新链表
newList = curr;
//将临时链表中的节点重新赋给当前链表
curr = point;
}
return newList;
}
}
leetcode 第234题
题目
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
分析
解析一:
将链表遍历存储到数组中,使用头尾双指针遍历数组比较
解析二:
使用快慢指针,找到中间节点,反转后一半链表,将慢指针复原到链表头部,进行比较,直到反转部分为空
代码
/**
* @author zwj
* @description 回文链表
* @location leetcode 第234题
* @date 2022/7/26 - 17:07
*/
public class Palindrome {
/**
* 使用数组保存链表数据进行比较
* @param head
* @return
*/
public static boolean isPalindromeByArray(ListNode head){
if (head == null) return false;
int length = 0;
int location = 0;
ListNode curr = head;
while(curr != null){
length++;
curr = curr.next;
}
int[] values = new int[length];
while (head != null){
values[location] = head.val;
location++;
head = head.next;
}
//创建首尾指针
for (int pre = 0, tail = length - 1; pre < tail; pre++, tail--) {
if (values[pre] != values[tail]){
return false;
}
}
return true;
}
/**
* 通过反转后半部分链表进行比较
* @param head
* @return
*/
public static boolean isPalindromeByReverse(ListNode head){
if (head == null) return false;
//找到反转链表的头部
ListNode reverseHead = getMidNode(head);
//反转链表
ListNode reverseList = getReverseList(reverseHead);
//对比节点数据
while (reverseList != null){
if (head.val != reverseList.val){
return false;
}
head = head.next;
reverseList = reverseList.next;
}
return true;
}
/**
* 找到反转链表的头部
* @param head
* @return
*/
public static ListNode getMidNode(ListNode head){
//创建快慢指针
ListNode slowPoint = head;
ListNode fastPoint = head;
/**
* 如果next为空,则证明为偶数个,为空退出循环,那么slowPoint下一个就是反转链表的开头
* 如果next.next为空,则证明为奇数个,那么slowPoint下一个就是反转链表的头
*/
while (fastPoint.next != null && fastPoint.next.next != null){
slowPoint = slowPoint.next;
fastPoint = fastPoint.next.next;
}
return slowPoint.next;
}
/**
* 反转链表
* @param head
* @return
*/
public static ListNode getReverseList(ListNode head){
//创建新链表
ListNode newList = null;
//创建原链表指针
ListNode curr = head;
//创建临时链表
ListNode st;
while (curr != null){
st = curr.next;
curr.next = newList;
newList = curr;
curr = st;
}
return newList;
}
}
leecode 第876题
题目
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
分析
使用快慢指针,如果快指针.next为空则为偶数,如果快指针.next.next为空,则为奇数
奇数情况下,慢指针就是中间节点,偶数情况下,当前慢指针节点和.next节点是中间节点,返回第二个中间节点
综上,当为奇数时,返回慢指针节点,当为偶数时,返回慢指针的下一个节点
所以判断,如果快指针的下一个节点为空,直接返回慢指针,否则判断下下个节点为空,直接返回慢指针的下一个节点
代码
/**
* @author zwj
* @description
* @date 2022/7/27 - 10:10
*/
public class MidNode {
public static ListNode getMidNode(ListNode head){
if (head == null) return null;
//创建快慢指针
ListNode slowPoint = head;
ListNode fastPoint = head;
//判断快指针的下一个节点
while (fastPoint.next != null){
//判断快指针的下下一个节点
if (fastPoint.next.next != null){
slowPoint = slowPoint.next;
fastPoint = fastPoint.next.next;
}else {
return slowPoint.next;
}
}
return slowPoint;
}
}
剑指offer 第22题
题目
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点
分析
# 解析
遍历链表,确认链表个数,length - k找到节点
代码
/**
* @author zwj
* @description
* @date 2022/7/27 - 10:36
*/
public class FromEndKNode {
public static ListNode getEndKNode(ListNode head, int k){
if (head == null) return null;
int length = 0;
//创建当前链表指针
ListNode point = head;
while (point != null){
length++;
point = point.next;
}
for (int i = 0; i < length - k; i++) {
head = head.next;
}
return head;
}
}
概念
栈:后进先出 LIFO
队列:先进先出 FIFO
leetcode 第232题
题目
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
分析
使用两个栈,一个作为输入栈,一个作为输出栈,每次使用输出栈时,判断输出栈是否为空,如果为空,从输入栈导出一次数据
代码
package leetcode.stack;
import java.util.Iterator;
import java.util.Stack;
/**
* @author zwj
* @description 用栈实现队列
* @location leetcode 第232题
* @date 2022/7/28 - 15:16
*/
public class MyQueue {
private static Stack<Integer> inStack;
private static Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (outStack.empty()){
outPush();
}
return outStack.pop();
}
public int peek() {
if (outStack.empty()){
outPush();
}
return outStack.peek();
}
public boolean empty() {
if (inStack.empty() && outStack.empty()){
return true;
}else {
return false;
}
}
public static void outPush(){
Iterator<Integer> iterator = inStack.iterator();
while (iterator.hasNext()){
outStack.push(inStack.pop());
}
}
}
leetcode 第394题
题目
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
分析
由题可知,每一对中括号内的值重复出现k次
所以,每遇到闭中括号,向前找到对应的开中括号,然后再找到开中括号前的数字,就可以
所以适合使用栈
遇到闭中括号之前一直压栈,直到闭中括号出现,出栈,放入队列,队列先进先出,使用springbuilder组成字符串,然后再出栈一次将数字出栈,循环数字后,将结果压栈,继续循环
注意:数字可能多位数,所以在遇到数字后,需要多次判断
package leetcode.stackandqueue.demo015;
import java.util.*;
/**
* @author zwj
* @description 字符串解码
* @location leetcode 第394题
* @date 2022/7/28 - 16:03
*/
public class DecodeString {
public static void main(String[] args) {
String s = "3[a]2[bc]";
decodeString(s);
}
public static String decodeString(String s){
//创建栈
Stack<String> stack = new Stack<>();
for (int i = 0; i < s.length();) {
char cur = s.charAt(i);
//判断当前字符是否为数字
if (Character.isDigit(cur)){
StringBuilder number = new StringBuilder();
//循环判断数字位数
while (Character.isDigit(s.charAt(i))){
number.append(s.charAt(i++));
}
//数字入栈
stack.push(number.toString());
}else if (Character.isLetter(cur) || cur == '['){
//字母或[入栈
stack.push(String.valueOf(s.charAt(i++)));
}else {
++i;
//创建新栈
Stack<String> component = new Stack<>();
//出栈
while (true){
String pop = stack.pop();
if (!pop.equals("[")){
component.push(pop);
}else {
break;
}
}
//次数出栈
int times = Integer.parseInt(stack.pop());
//获取字符串对象
String o = getString(component);
//创建builder
StringBuilder builder = new StringBuilder();
while (times-- > 0){
builder.append(o);
}
//入栈
stack.push(builder.toString());
}
}
Collections.reverse(stack);
return getString(stack);
}
private static String getString(Stack<String> stack) {
StringBuilder sb = new StringBuilder();
while (!stack.empty()){
sb.append(stack.pop());
}
return sb.toString();
}
}
概念
树是n(n>=0)个结点的有限集。n=0时称为空树
在任意一棵非空树中:
1)有且只有一个特定的称为根(root)的结点
2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2...,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
1.树的度
节点孩子的个数是节点的度,树的度为节点中最大的度
2.结点间关系
父子结点,兄弟结点,祖先结点
3.树的深度
树的层数
树的表示
双亲表示法
编号 | data | parent |
---|---|---|
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 1 |
孩子表示法
data | degree(度) | child1 | child2 | child3 | … | childn |
---|---|---|---|---|---|---|
概念
二叉树每个结点最多有两个子结点
特殊二叉树
1.斜树
2.满二叉树: 所有的结点都有左右子结点且所有叶子结点都在同一层,非叶子结点的度都是2
结点个数等于 2的深度次方-1
3.完全二叉树:和满二叉树按一样的顺序编号,编号位置一致
除了叶子结点,其他节点必须和满二叉树一致
leetcode 第94题
题目
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
分析
中序遍历: 左根右
循环遍历左子树,一直到左叶子结点
但是由于是左根右,所以左结点遍历后,需要返回一步到根结点
所以,使用栈将每个根结点压栈,每退回一步,栈出栈一个结点。
根结点出栈后,还要看右结点,遍历右结点有没有左子结点
递归
package leetcode.tree.demo016;
import leetcode.tree.node.TreeNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author zwj
* @description 中序遍历二叉树
* @location leetcode 第94题
* @date 2022/7/29 - 14:19
*/
public class MidOrderTraversal {
public static List<Integer> MidOrder(TreeNode root){
//创建list
List<Integer> list = new ArrayList<>();
if (root == null) return list;
//创建栈存储根节点
Stack<TreeNode> stack = new Stack<>();
//循环判断条件,root不等于null或stack中有结点
while (root != null || !stack.isEmpty()){
//循环遍历二叉树左结点
while (root != null){
//将根节点存入栈
stack.push(root);
//遍历左节点
root = root.left;
}
//出循环,当前root为空,根节点就是最左叶子结点,获取当前左结点的根节点
root = stack.pop();
//获取当前最左叶子结点数据
list.add(root.val);
//将当前结点转为右子结点,重新遍历左节点,如果右结点为空,会跳过循环再次出栈,不为空,则找到最左叶子结点,重复操作
root = root.right;
}
//当栈内为空时,root为空,说明已经没有节点,输出list
return list;
}
//不能用static,因为测试用例有多个,要不然需要清空list
public List<Integer> list = new ArrayList<>();
/**
* 递归
* @param root
* @return
*/
public List<Integer> inorderTraversal(TreeNode root){
if (root == null) return list;
list = inorderTraversal(root.left);
list.add(root.val);
list = inorderTraversal(root.right);
return list;
}
}
leetcode 第144题
题目
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
分析
前序遍历: 根左右
先访问根节点,在访问左右结点
同中序遍历一样遍历,只是添加顺序做一下改变,先保存根数据
从根节点开始一直向左遍历,同时添加数据,到叶子节点时,pop根结点,指向右结点,继续遍历左节点
代码
package leetcode.tree.demo016;
import leetcode.tree.node.TreeNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author zwj
* @description 前序遍历
* @location leetcode 第144题
* @date 2022/7/29 - 15:43
*/
public class BeforeOrderTraversal {
public static List<Integer> beforeOrder(TreeNode root){
List<Integer> list = new ArrayList<>();
if (root == null) return list;
//创建栈存储根结点
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.empty()){
while(root != null){
//直接将值存储
list.add(root.val);
//将根节点存入栈
stack.push(root);
//左节点遍历
root = root.left;
}
//将根节点出栈
root = stack.pop();
//开始遍历右结点
root = root.right;
}
//输出list
return list;
}
public List<Integer> list = new ArrayList<>();
/**
* 递归
* @param root
* @return
*/
public List<Integer> preorderTraversal(TreeNode root){
if (root == null) return list;
list.add(root.val);
list = preorderTraversal(root.left);
list = preorderTraversal(root.right);
return list;
}
}
leetcode 第145题
题目
给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
分析
后序遍历:左右根
一直向左子结点遍历,直到左叶子结点,栈存储根节点,拿到子叶子结点后,pop根节点,在push根结点,向右结点遍历
因为左右根,所以,遍历右结点时,需要做记录,如果右结点为空,直接到根,如果右结点已经拿过值,直接返回根
package leetcode.tree.demo016;
import leetcode.tree.node.TreeNode;
import java.util.*;
/**
* @author zwj
* @description 后序遍历
* @location leetcode 第145题
* @date 2022/7/29 - 16:02
*/
public class AfterOrderTraversal {
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
TreeNode one = new TreeNode(1);
TreeNode two = new TreeNode(2, null, null);
root.left = one;
root.right = null;
one.left = null;
one.right = two;
afterOrder(root);
}
public static List<Integer> afterOrder(TreeNode root) {
List<Integer> list = new ArrayList<>();
if (root == null) return list;
//创建栈
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> rootStack = new Stack<>();
TreeNode st;
while (root != null || !stack.empty() || !rootStack.empty()){
while (root != null){
stack.push(root);
root = root.left;
}
if (!stack.empty()){
root = stack.pop();
rootStack.push(root);
root = root.right;
}
if (root == null){
list.add(rootStack.pop().val);
}else {
TreeNode pop = rootStack.pop();
pop.left = null;
pop.right = null;
stack.push(pop);
}
}
return list;
}
public static List<Integer> afterOrderTwo(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
//创建栈
Deque<TreeNode> stack = new LinkedList<TreeNode>();
//创建结点
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
//判断结点为空或者结点的右结点已经出现过
// root.right = prev这个条件,就是为了放置重复遍历结点,因为每个push到栈的结点的子节点可能重复
if (root.right == null || root.right == prev) {
//添加数据
res.add(root.val);
//将当前结点赋给prev
prev = root;
//使用root = null 条件释放栈中的结点
root = null;
} else {
//当前结点还有子节点,将当前结点入栈
stack.push(root);
//遍历子节点
root = root.right;
}
}
return res;
}
public List<Integer> list = new ArrayList<>();
/**
* 递归
* @param root
* @return
*/
public List<Integer> postorderTraversal(TreeNode root){
if (root == null) return list;
list = postorderTraversal(root.left);
list = postorderTraversal(root.right);
list.add(root.val);
return list;
}
}
leetcode 第101题
题目
给你一个二叉树的根节点 root , 检查它是否轴对称。
分析
将root结点下的左右结点分成两个根节点,遍历结点并对比,采用递归
代码
/**
* @author zwj
* @description 对称二叉树
* @location leetcode 第101题
* @date 2022/7/29 - 17:55
*/
public class SymmetricTree {
public boolean isSymmetric(TreeNode left, TreeNode right){
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(left);
queue.offer(right);
while (!queue.isEmpty()){
left = queue.poll();
right = queue.poll();
if (left == null && right == null){
continue;
}
if (left == null || right == null || left.val != right.val){
return false;
}
queue.offer(left.left);
queue.offer(right.right);
queue.offer(left.right);
queue.offer(right.left);
}
return true;
}
//递归
public static boolean isSymmetric(TreeNode left, TreeNode right){
if (left == null && right == null) return true;
if (left == null || right == null) return false;
boolean a = isSymmetric(left.left, right.right);
boolean b = isSymmetric(left.right, right.left);
if (a == true && b == true){
if (left.val == right.val){
return true;
}
}
return false;
}
}
leetcode第104题
题目
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
分析
使用递归
如果我们知道了左子树和右子树的最大深度 l 和 r,
那么该二叉树的最大深度即为
max(l,r)+1
而左子树和右子树的最大深度又可以以同样的方式进行计算。
因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。
具体而言,在计算当前二叉树的最大深度时,
可以先递归计算出其左子树和右子树的最大深度,
然后在 O(1)O(1) 时间内计算出当前二叉树的最大深度。
递归在访问到空节点时退出。
代码
/**
* @author zwj
* @description 二叉树最大深度
* @date 2022/8/3 - 9:53
*/
public class MaxDepthTree {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
leetcode 第110题
题目
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
分析
根据深度递归遍历,可以知道,递归遍历是对每一个结点的左右结点进行遍历,
所以可以利用这一点,对每一个结点的左右结点遍历,直到为空,
判断每一个结点的左右节点的高度差,返回值
代码
/**
* @author zwj
* @description 平衡二叉树
* @location leetcode 第110题
* @date 2022/8/3 - 10:12
*/
public class BalancedTree {
public boolean isBalanced(TreeNode root){
if (root == null) return true;
return depth(root) != -1;
}
public int depth(TreeNode root){
if (root == null) return 0;
int left = depth(root.left);
int right = depth(root.right);
if (Math.abs(left - right) > 1 || left == -1 || right == -1){
return -1;
}
return Math.max(left, right) + 1;
}
}
leetcode 第226题
题目
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点
分析
由题可知,做法就是循环迭代将左右结点调换
代码
/**
* @author zwj
* @description 翻转二叉树
* @location leetcode 第226题
* @date 2022/8/3 - 10:34
*/
public class ReverseTree {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
//对每个结点施加操作
invertTree(root.left);
invertTree(root.right);
//翻转规则
TreeNode st = root.left;
root.left = root.right;
root.right = st;
return root;
}
}