• 算法宝典——Java版本(持续更新)


    注:由于字数的限制,我打算把算法宝典做成一个系列,一篇文章就20题!!!

    目录

    一、链表的算法题(目前10道)

    1. 移除链表元素(力扣;思路:前后指针)

    2. 反转一个单链表 (力扣;思路:头插法)

    3. 链表的中间结点(力扣;思路:快慢指针)

    4. 链表中倒数第k个结点(牛客网;思路:①快慢指针、②倒数第几个与步数的关系)

    5. 合并两个有序链表(力扣)

    6. 链表分割(牛客网)

    7. 相交链表(力扣)

    8.  链表的回文结构(牛客网)

    9.  环形链表(力扣;思路:快慢指针、数学追击问题)

    10.  环形链表 II(力扣;思路:快慢指针、数学追击问题★★★☆☆)

    二、栈的算法题(目前4道)

    1. 有效的括号(力扣)

    2. 逆波兰表达式求值(力扣)

    3. 栈的压入、弹出序列(牛客网)

    4.  最小栈(力扣)

    三、队列的算法题(目前3道)

    1. 设计循环队列(力扣)

    2. 用队列实现栈(力扣)

    3. 用栈实现队列(力扣)

    四、二叉树的算法题(目前3道) 

    1. 相同的树(力扣)

    2. 另一棵树的子树(力扣)

    3. 翻转二叉树(力扣)


    一、链表的算法题(目前10道)

    1. 移除链表元素(力扣;思路:前后指针)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/description/

    题目:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

    思路:

    ed126bc284fa4e21a28ee2a67bd42bbe.png

    代码:

    1. /**
    2. * Definition for singly-linked list.
    3. * public class ListNode {
    4. * int val;
    5. * ListNode next;
    6. * ListNode() {}
    7. * ListNode(int val) { this.val = val; }
    8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    9. * }
    10. */
    11. class Solution {
    12. public ListNode removeElements(ListNode head, int val) {
    13. if(head == null){
    14. return null;
    15. }
    16. ListNode prev = head;
    17. ListNode cur = head.next;
    18. while(cur != null){
    19. if(cur.val == val){
    20. prev.next = cur.next;
    21. cur = cur.next;
    22. }else{
    23. cur = cur.next;
    24. prev = prev.next;
    25. }
    26. }
    27. //最后处理第一个节点
    28. if(head.val == val){
    29. head = head.next;
    30. }
    31. return head;
    32. }
    33. }

    运行结果:

    e9600f94041a4a6faab8188fc48ed21e.png

    时间复杂度和空间复杂度:

    (1)时间复杂度:O(N);(2)空间复杂度:O(1)

    2. 反转一个单链表 (力扣;思路:头插法)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-linked-list/description/

    题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

    思路:

    d60d3af6c8704f86ba3f33535e674250.png

    代码: 

    1. /**
    2. * Definition for singly-linked list.
    3. * public class ListNode {
    4. * int val;
    5. * ListNode next;
    6. * ListNode() {}
    7. * ListNode(int val) { this.val = val; }
    8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    9. * }
    10. */
    11. class Solution {
    12. public ListNode reverseList(ListNode head) {
    13. //特殊情况一:如果链表为空,就不需要返回
    14. if(head == null){
    15. return null;
    16. }
    17. //特殊情况二:只有一个节点
    18. if(head.next == null){
    19. return head;
    20. }
    21. ListNode cur = head.next;//把第二个节点地址保存起来
    22. head.next = null;//到时第一个节点的next肯定为空,在这里就可以提前改了
    23. //进入循环,开始进行头插法
    24. while(cur != null){
    25. ListNode curNext = cur.next;//保存cur指向的节点
    26. cur.next = head;//将第一个节点的地址赋值给cur.next
    27. head = cur;
    28. cur = curNext;
    29. }
    30. return head;
    31. }
    32. }

    运行结果:

    da607258f141437ba49ed38bb427e9a6.png

    时间复杂度和空间复杂度:

    (1)时间复杂度:O(N);空间复杂度:O(1)

    3. 链表的中间结点(力扣;思路:快慢指针)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-linked-list/description/

    题目:思路给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

    思路:

    23501745df9f4ef48f3b1a9ce2b5a968.png

    代码: 

    1. /**
    2. * Definition for singly-linked list.
    3. * public class ListNode {
    4. * int val;
    5. * ListNode next;
    6. * ListNode() {}
    7. * ListNode(int val) { this.val = val; }
    8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
    9. * }
    10. */
    11. class Solution {
    12. public ListNode middleNode(ListNode head) {
    13. //特殊情况:如果只有一个结点
    14. if(head.next == null){
    15. return head;
    16. }
    17. ListNode quick = head;//快指针
    18. ListNode slow = head;//慢指针
    19. //有两种条件都成立表示快指针不走了,(1)quick为空 (2)quick.next为空
    20. while(quick != null && quick.next != null){
    21. quick = quick.next.next;
    22. slow = slow.next;
    23. }
    24. return slow;
    25. }
    26. }

    运行结果:

    a0152355dc99421099a201c1f351cf82.png

    时间复杂度和空间复杂度:

    (1)时间复杂度:O(N);(2)空间复杂度:O(1)

    4. 链表中倒数第k个结点(牛客网;思路:①快慢指针、②倒数第几个与步数的关系)

    题目跳转链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking题目:输入一个链表,输出该链表中倒数第k个结点。

    思路:

    1caa2144c9794d0ea1761e571db651e6.png

    代码: 

    思路一:

    1. import java.util.*;
    2. /*
    3. public class ListNode {
    4. int val;
    5. ListNode next = null;
    6. ListNode(int val) {
    7. this.val = val;
    8. }
    9. }*/
    10. public class Solution {
    11. public ListNode FindKthToTail(ListNode head, int k) {
    12. //输入值是否正确的判断
    13. if(k <= 0 || head == null){
    14. return null;
    15. }
    16. ListNode quick = head;
    17. ListNode slow = head;
    18. //先让quick走k - 1步
    19. while (k - 1 != 0){
    20. quick = quick.next;
    21. if(quick == null){
    22. return null;
    23. }
    24. k--;
    25. }
    26. //快慢指针一起走
    27. while(quick.next != null){
    28. quick = quick.next;
    29. slow = slow.next;
    30. }
    31. return slow;
    32. }
    33. }

    思路二:

    1. public ListNode findKthToTail2(int k) {
    2. //输入值是否正确的判断
    3. if(k <= 0 || head == null){
    4. return null;
    5. }
    6. int count = 0;
    7. ListNode cur = head;
    8. while(cur != null){
    9. count++;
    10. cur = cur.next;
    11. }
    12. int i = count - k;
    13. cur = head;
    14. if(i < 0){
    15. return null;
    16. }
    17. while(i > 0){
    18. cur = cur.next;
    19. i--;
    20. }
    21. return cur;
    22. }

    运行结果:

    804065225d0d4c5fb7bacc1db417990d.png

    时间复杂度和空间复杂度:

    (1)时间复杂度:O(N);(2)空间复杂度:O(1)

    5. 合并两个有序链表(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/merge-two-sorted-lists/description/

    题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    思路:

    7d4ffc8fe062452aa74b5217aa7ff812.png

    代码: 

    1. //合并两个升序链表,有问题
    2. public ListNode mergeTwoLists(ListNode list1, ListNode list2){
    3. //处理空表的情况
    4. if(list1 == null && list2 !=null){
    5. return list2;
    6. }else if(list1 != null && list2 == null){
    7. return list1;
    8. }else if(list1 == null&&list2 == null){
    9. return null;
    10. }
    11. //创建两个地址引用,指向两个链表的首元素结点
    12. ListNode cur1 = list1;
    13. ListNode cur2 = list2;
    14. ListNode cur3 = null;//始终指向新链表的终端结点
    15. //创建一个newList引用
    16. ListNode newList = null;
    17. if (cur1.val <= cur2.val){
    18. newList = cur1;
    19. cur1 = cur1.next;
    20. cur3 = newList;
    21. }else if(cur1.val >= cur2.val){
    22. newList = cur2;
    23. cur2 = cur2.next;
    24. cur3 = newList;
    25. }
    26. //开始比较
    27. while(cur1 != null && cur2 != null ){
    28. if(cur1.val <= cur2.val){
    29. cur3.next = cur1;
    30. cur3 = cur3.next;
    31. cur1 = cur1.next;
    32. }else if(cur1.val > cur2.val){
    33. cur3.next = cur2;
    34. cur3 = cur3.next;
    35. cur2 = cur2.next;
    36. }
    37. }
    38. //当cur1 == null或cur2 == null时,意味着已经到链表的结尾,此时还需要将没有连接上的链表(cur1或cur2)连接接上
    39. cur3.next = null;
    40. if(cur1 != null){
    41. cur3.next = cur1;
    42. }
    43. if(cur2 != null){
    44. cur3.next = cur2;
    45. }
    46. return newList;
    47. }

    运行结果:

    ce1ef861c2ef420a8c4f3b09d2f37d80.png

    6. 链表分割(牛客网)

    题目跳转链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking

    题目:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

    思路:

    b51b92ae44ce47a3b9f5141ab4eb7281.png

    代码: 

    1. /*
    2. public class ListNode {
    3. int val;
    4. ListNode next = null;
    5. ListNode(int val) {
    6. this.val = val;
    7. }
    8. }*/
    9. public class Partition {
    10. public ListNode partition(ListNode pHead, int x) {
    11. // write code here
    12. //把整个链表看成两部分,创建四个引用来分别指向这两部分
    13. ListNode bs = null;//小于x部分的起始引用
    14. ListNode be = null;//小于x部分的终端引用
    15. ListNode as = null;//大于或等于x部分的起始引用
    16. ListNode ae = null;//大于或等于x部分的终端引用
    17. ListNode cur = pHead;
    18. //开始循环
    19. while(cur != null){
    20. //有两种可能性,小于x或者大于等于x
    21. if(cur.val < x){
    22. //这部分也有两种情况,①当bs和be都为空,②bs不会空
    23. if(bs == null){
    24. bs = cur;
    25. be = cur;
    26. }else{
    27. be.next = cur;
    28. be = be.next;
    29. }
    30. }else{
    31. if(as == null){
    32. as = cur;
    33. ae = cur;
    34. }else{
    35. ae.next = cur;
    36. ae = ae.next;
    37. }
    38. }
    39. cur = cur.next;
    40. }
    41. //有可能不会同时存在小于x和大于等于x的数据
    42. if(bs == null){
    43. return as;
    44. }
    45. //特殊情况:如果第一段不为空
    46. be.next = as;
    47. //如果第二段为空
    48. if(as != null){
    49. ae.next = null;
    50. }
    51. return bs;
    52. }
    53. }

    运行结果:

    f5ac474b27be4c8daccd8d71d9f80587.png

    7. 相交链表(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/intersection-of-two-linked-lists/description/题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

    题目数据 保证 整个链式结构中不存在环。

    注意,函数返回结果后,链表必须 保持其原始结构 。

    思路:

    783265d812214c179bf5e2a855833421.png

    代码: 

    1. public MySinglelist.ListNode getIntersectionNode(MySinglelist.ListNode headA, MySinglelist.ListNode headB) {
    2. int lenA = 0;//用来记录长度
    3. int lenB = 0;
    4. MySinglelist.ListNode pl = headA;
    5. MySinglelist.ListNode ps = headB;
    6. //计算pl和ps的链表长度
    7. while(pl != null){
    8. lenA++;
    9. pl = pl.next;
    10. }
    11. while (ps != null){
    12. lenB++;
    13. ps = ps.next;
    14. }
    15. //1. len一定是一个正数 2.pl指向的链表一定是最长的 ps 指向的链表一定是最短的
    16. pl = headA;
    17. ps = headB;
    18. //计算彼此之间的差值
    19. int len = lenA - lenB;
    20. if(len < 0){
    21. pl = headB;
    22. ps = headA;
    23. len = lenB - lenA;
    24. }
    25. //先让长度多的链表先走len步
    26. while(len != 0){
    27. pl = pl.next;
    28. len--;
    29. }
    30. //开始找相交的节点
    31. while(pl != ps){
    32. ps = ps.next;
    33. pl = pl.next;
    34. }
    35. //pl == ps
    36. /* if(pl == null) {
    37. return null;
    38. }*/
    39. return pl;
    40. }

    运行结果:

    43040a123a13462f9f5a6f65b13d731d.png

    8.  链表的回文结构(牛客网)

    题目跳转链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking题目:对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

    思路:

    定义快慢指针:

    (1)找到中间结点

    (2)翻转慢指针到快指针部分的结点

    (3)首尾互相判断是否符合回文结构

    代码: 

    1. public boolean chkPalindrome(ListNode head) {
    2. if(head == null){
    3. return false;
    4. }
    5. if(head.next == null){
    6. return true;
    7. }
    8. // write code here
    9. //先定义快慢指针
    10. ListNode quick = head;
    11. ListNode slow = head;
    12. //1. 找中间结点
    13. while(quick != null && quick.next != null){
    14. quick = quick.next.next;
    15. slow = slow.next;
    16. }
    17. //2. 翻转
    18. ListNode cur = slow.next;
    19. while(cur != null){
    20. ListNode curNext = cur.next;//先记录下来后一个结点
    21. cur.next = slow;//实现翻转
    22. slow = cur;//保存上一个结点的地址
    23. cur = curNext;//找到下一个结点
    24. }
    25. //3.开始判断
    26. cur = head;
    27. while(cur != slow){
    28. //如果不是不相等,返回false
    29. if(cur.val != slow.val){
    30. return false;
    31. }
    32. //判断偶数的情况:
    33. if(cur.next == slow){
    34. return true;
    35. }
    36. cur = cur.next;
    37. slow = slow.next;
    38. }
    39. return true;
    40. }

    运行结果:

    ee73ea872379449095990d7f548dc1a3.png

    9.  环形链表(力扣;思路:快慢指针、数学追击问题)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle/description/题目:给你一个链表的头节点 head ,判断链表中是否有环。

    如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

    如果链表中存在环 ,则返回 true 。 否则,返回 false 。

    思路:

    378a0c7c403c40eab57d7af7e1610047.png

    代码: 

    1. //判断链表是否有环
    2. //第一种写法
    3. public boolean hasCycle() {
    4. ListNode fast = head;
    5. ListNode slow = head;
    6. while(fast != null && fast.next != null){
    7. fast = fast.next.next;
    8. slow = slow.next;
    9. if(fast == slow){
    10. return true;
    11. }
    12. }
    13. return false;
    14. }
    15. //第二种写法
    16. public boolean hasCycle2() {
    17. ListNode fast = head;
    18. ListNode slow = head;
    19. while(fast != null && fast.next != null){
    20. fast = fast.next.next;
    21. slow = slow.next;
    22. if(fast == slow){
    23. break;
    24. }
    25. }
    26. if(fast == null || fast.next == null){
    27. return false;
    28. }
    29. return true;
    30. }

    运行结果:

    878a5c809c0e44ff8b8c02251a47fbe5.png

    【扩展问题】

    • 为什么快指针每次走两步,慢指针走一步可以?

    假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

    • 快指针一次走3步,走4步,...n步行吗?

    2141faa24f3e41a0bdb74883804c7102.png

    10.  环形链表 II(力扣;思路:快慢指针、数学追击问题★★★☆☆)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle-ii/题目:给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

    如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

    不允许修改 链表。

    思路:

    23cb4ad07e7d4fd2b72a60f5902d5db4.png

    代码: 

    1. public ListNode detectCycle() {
    2. //找到相遇点
    3. ListNode fast = head;
    4. ListNode slow = head;
    5. while(fast != null && fast.next != null){
    6. fast = fast.next.next;
    7. slow = slow.next;
    8. if(fast == slow){
    9. break;
    10. }
    11. }
    12. if(fast == null || fast.next == null){
    13. return null;
    14. }
    15. //slow指针从头走
    16. slow = head;
    17. //fast指针从相遇点开始走
    18. //因为X = (N - 1)C + Y
    19. while (fast != slow){
    20. fast = fast.next;
    21. slow = slow.next;
    22. }
    23. return fast;
    24. }

    运行结果:

    a230f288c4cc44bd8dd1394a3502b486.png

    二、栈的算法题(目前4道)

    1. 有效的括号(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/valid-parentheses/题目:给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

    有效字符串需满足:

    1. 左括号必须用相同类型的右括号闭合。
    2. 左括号必须以正确的顺序闭合。
    3. 每个右括号都有一个对应的相同类型的左括号。

    思路:

    代码: 

    1. public static boolean isValid(String s) {
    2. //创建一个栈来存储括号
    3. Stack stack = new Stack<>();
    4. //用for循环进行拆分String
    5. for (int i = 0; i < s.length(); i++) {
    6. char ch = s.charAt(i);
    7. //如果字符是左括号就入栈
    8. if (ch == '(' || ch == '{' || ch == '[') {
    9. stack.push(ch);
    10. } else {
    11. //如果字符是右括号就执行else
    12. //如果栈是空的,就意味着右括号之前没有左括号
    13. if (stack.empty()) {
    14. return false;
    15. }
    16. //先看下栈顶元素是不是满足条件的左括号
    17. char ch2 = stack.peek();
    18. //进行匹配
    19. if (ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{' || ch == ']' && ch2 == '['){
    20. //满足条件,栈顶的左括号跳出
    21. stack.pop();
    22. }else{
    23. return false;//如果不满足条件,返回false
    24. }
    25. }
    26. }
    27. //假设右括号都匹配完了,还是有左括号加入,也就是栈不为空
    28. if(!stack.empty()){
    29. return false;
    30. }
    31. return true;
    32. }

    运行结果:

    2. 逆波兰表达式求值(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/evaluate-reverse-polish-notation/题目:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

    请你计算该表达式。返回一个表示表达式值的整数。

    注意:

    • 有效的算符为 '+''-''*' 和 '/' 。
    • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
    • 两个整数之间的除法总是 向零截断 。
    • 表达式中不含除零运算。
    • 输入是一个根据逆波兰表示法表示的算术表达式。
    • 答案及所有中间计算结果可以用 32 位 整数表示。

    思路:

    代码: 

    1. public int evalRPN(String[] tokens) {
    2. //首先创建一个栈
    3. Stack stack = new Stack<>();
    4. //遍历数组
    5. for(String x : tokens){
    6. if (!isOperation(x)){
    7. //如果不是操作符就压入栈
    8. stack.push(Integer.parseInt(x));//解析字符串
    9. }else{
    10. //如果遍历到操作符
    11. //弹出两个数
    12. int num2 = stack.pop();//作为右操作数
    13. int num1 = stack.pop();//作为左操作数
    14. switch (x){
    15. case "+":
    16. stack.push(num1 + num2);
    17. break;
    18. case "-":
    19. stack.push(num1 - num2);
    20. break;
    21. case "*":
    22. stack.push(num1 * num2);
    23. break;
    24. case "/":
    25. stack.push(num1 / num2);
    26. break;
    27. }
    28. }
    29. }
    30. //最后弹出最后一个数,就可以得到结果了
    31. return stack.pop();
    32. }
    33. //判断传入的字符是不是操作数
    34. private boolean isOperation(String x){
    35. if(x.equals("+") || x.equals("-") || x.equals("*") || x.equals("/")){
    36. return true;
    37. }
    38. return false;
    39. }

    运行结果:

    3. 栈的压入、弹出序列(牛客网)

    题目跳转链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

    1. 0<=pushV.length == popV.length <=1000

    2. -1000<=pushV[i]<=1000

    3. pushV 的所有数字均不相同

    思路:此题的思路比较简单,就不作图了,代码的注释有写哈哈哈。

    代码: 

    1. public boolean IsPopOrder (int[] pushV, int[] popV) {
    2. //pushV 和 popV 的元素数量都一致
    3. // write code here
    4. //首先创建一个栈
    5. Stack stack = new Stack<>();
    6. int j = 0;
    7. for (int i = 0;i < pushV.length;i++){
    8. stack.push(pushV[i]);
    9. //这段代码要注意异常的抛出,得先知道栈不为空,才可以跳出栈顶元素
    10. while(j < popV.length && !stack.empty() && stack.peek().equals(popV[j]) ){
    11. stack.pop();
    12. j++;
    13. }
    14. }
    15. return stack.empty();//如果stack为空了,就是栈内没元素了,都匹配上了
    16. }

    运行结果:

    4.  最小栈(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/min-stack/题目:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

    实现 MinStack 类:

    • MinStack() 初始化堆栈对象。
    • void push(int val) 将元素val推入堆栈。
    • void pop() 删除堆栈顶部的元素。
    • int top() 获取堆栈顶部的元素。
    • int getMin() 获取堆栈中的最小元素。

    思路:

    虽然内部是用了两个栈,但是在外部看就是一个栈!

    代码: 

    1. public class MinStack {
    2. private Stack stack;
    3. private Stack minStack;
    4. //初始化两个栈
    5. public MinStack() {
    6. stack = new Stack<>();
    7. minStack = new Stack<>();
    8. }
    9. //压栈
    10. public void push(int val) {
    11. //先压入普通栈
    12. stack.push(val);
    13. //如果最小栈为空,直接压入最小栈进行维护
    14. if(minStack.empty()){
    15. minStack.push(val);
    16. }else{
    17. //如果最小栈有值,那么就看看如今想压入栈的元素与栈顶元素的大小
    18. //栈顶元素是在minStack中最小的元素,也是stack中最小的元素
    19. if (val <= minStack.peek()){
    20. minStack.push(val);
    21. }
    22. }
    23. }
    24. //出栈
    25. public void pop() {
    26. if(!stack.empty()){
    27. Integer val = stack.pop();//取出stack的栈顶元素
    28. //如果stack的栈顶元素是minStack栈顶元素的,那么minStack的栈顶元素也要取出
    29. if(val.equals(minStack.peek())){//这里必须用equals,因为Integer在元数据区中只有-128~127
    30. minStack.pop();
    31. }
    32. }
    33. }
    34. //查看栈顶元素
    35. public int top() {
    36. return stack.peek();
    37. }
    38. public int getMin() {
    39. return minStack.peek();
    40. }
    41. }

    运行结果:

    三、队列的算法题(目前3道)

    1. 设计循环队列(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/design-circular-queue/

    题目:设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

    循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

    你的实现应该支持如下操作:

    • MyCircularQueue(k): 构造器,设置队列长度为 k 。
    • Front: 从队首获取元素。如果队列为空,返回 -1 。
    • Rear: 获取队尾元素。如果队列为空,返回 -1 。
    • enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
    • deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
    • isEmpty(): 检查循环队列是否为空。
    • isFull(): 检查循环队列是否已满。

    思路:

    循环队列两个特殊的状态:队空和队满(rear的情况和front类似)。 由图3-5可以看出,循环队列必须损失一个存储空间,如果空白处也存入元素,则队满的条件也成了front==rear,即和队空条件相同,那么就无法区分队空和队满了

    (1)三个状态

    1)队空状态:qu.rear==qu.front。

    2)队满状态:(qu.rear+1)%maxSizequ == front。

    3)长度(多少个元素):(rear-front+maxsize)%maxsize;防止rear-front为负,所以要加maxsize

    (2)两个操作

    1)元素x进队操作(移动队尾指针)。

    qu.rear=(qu.rear+1)%maxSize;qu.data[qu.rear]=x;

    2)元素x出队操作(移动队首指针)。

    qu.front=(qu.front+1)%maxSize;x=qu.data[qu.front];

    队首指针指向的位置不能有数据!!!

    代码:

    1. //循环队列
    2. public class MyCircularQueue {
    3. private int elem[];
    4. private int front;//表示队列的头
    5. private int rear;//表示队列的尾
    6. //创建循环队列
    7. public MyCircularQueue(int k) {
    8. //如果是浪费空间,这里必须多加一个1
    9. this.elem = new int[k + 1];
    10. }
    11. //判断队列是否为满
    12. public boolean isFull() {
    13. return (rear + 1) % elem.length == front;
    14. }
    15. //入队列
    16. public boolean enQueue(int value) {
    17. //1. 检查是否队列是满的
    18. if (isFull()) {
    19. return false;
    20. }
    21. elem[rear] = value;
    22. rear = (rear + 1) % elem.length;
    23. return true;
    24. }
    25. //出队列
    26. public boolean deQueue() {
    27. //队列为空
    28. if (isEmpty()) {
    29. return false;
    30. }
    31. front = (front + 1) % elem.length;
    32. return true;
    33. }
    34. //得到队头元素
    35. public int Front() {
    36. if (isEmpty()) {
    37. return -1;
    38. }
    39. return elem[front];
    40. }
    41. //得到队尾元素
    42. public int Rear() {
    43. if(isEmpty()) {
    44. return -1;
    45. }
    46. int index = (rear == 0) ? elem.length - 1 : rear-1;
    47. return elem[index];
    48. }
    49. //判读队列是否为空
    50. public boolean isEmpty() {
    51. return front == rear;
    52. }
    53. }

    运行结果:

    2. 用队列实现栈(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/implement-stack-using-queues/

    题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

    实现 MyStack 类:

    • void push(int x) 将元素 x 压入栈顶。
    • int pop() 移除并返回栈顶元素。
    • int top() 返回栈顶元素。
    • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

    思路:

    代码:

    1. import java.util.LinkedList;
    2. import java.util.Queue;
    3. import java.util.Stack;
    4. public class MyStack2 {
    5. //需要两个队列才能模拟栈
    6. private Queue qu1;
    7. private Queue qu2;
    8. public MyStack2() {
    9. qu1 = new LinkedList<>();
    10. qu2 = new LinkedList<>();
    11. }
    12. public void push(int x) {
    13. if(!qu1.isEmpty()){
    14. qu1.offer(x);
    15. }else if(!qu2.isEmpty()){
    16. qu2.offer(x);
    17. }else {
    18. qu1.offer(x);
    19. }
    20. }
    21. public int pop() {
    22. if(empty()){
    23. return -1;//两个队列都为空,意味着当前的栈为空
    24. }
    25. if(!qu1.isEmpty()){
    26. int size = qu1.size();
    27. for(int i = 0;i < size - 1;i++){
    28. int val = qu1.poll();
    29. qu2.offer(val);
    30. }
    31. return qu1.poll();
    32. }else{
    33. int size = qu2.size();
    34. for(int i = 0;i1;i++){
    35. int val = qu2.poll();
    36. qu1.offer(val);
    37. }
    38. return qu2.poll();
    39. }
    40. }
    41. public int top() {
    42. if(empty()){
    43. return -1;
    44. }
    45. int val = 0;
    46. if(!qu1.isEmpty()){
    47. int size = qu1.size();
    48. for(int i = 0;i < size;i++){
    49. val = qu1.poll();
    50. qu2.offer(val);
    51. }
    52. return val;
    53. }else{
    54. int size = qu2.size();
    55. for(int i = 0;i < size;i++){
    56. val = qu2.poll();
    57. qu1.offer(val);
    58. }
    59. return val;
    60. }
    61. }
    62. public boolean empty() {
    63. return qu1.isEmpty() && qu2.isEmpty();
    64. }
    65. }

    运行结果:

    3. 用栈实现队列(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/implement-queue-using-stacks/题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

    实现 MyQueue 类:

    • void push(int x) 将元素 x 推到队列的末尾
    • int pop() 从队列的开头移除并返回元素
    • int peek() 返回队列开头的元素
    • boolean empty() 如果队列为空,返回 true ;否则,返回 false

    说明:

    • 你 只能 使用标准的栈操作 —— 也就是只有 push to toppeek/pop from topsize, 和 is empty 操作是合法的。
    • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

    思路:

    代码:

    1. import java.util.Stack;
    2. public class MyQueue2 {
    3. //创建两个栈
    4. private Stack s1;
    5. private Stack s2;
    6. public MyQueue2() {
    7. s1 = new Stack<>();
    8. s2 = new Stack<>();
    9. }
    10. //入队
    11. public void push(int x) {
    12. s1.push(x);
    13. }
    14. //出队
    15. public int pop() {
    16. //栈空判断
    17. if(empty()){
    18. return -1;
    19. }
    20. if(s2.empty()){
    21. while(!s1.empty()){
    22. s2.push(s1.pop());
    23. }
    24. }
    25. return s2.pop();
    26. }
    27. //返回队头结点
    28. public int peek() {
    29. //栈空判断
    30. if(empty()){
    31. return -1;
    32. }
    33. if(s2.empty()){
    34. while(!s1.empty()){
    35. s2.push(s1.pop());
    36. }
    37. }
    38. return s2.peek();
    39. }
    40. public boolean empty() {
    41. return s1.empty() && s2.empty();
    42. }
    43. }

    运行结果:

    四、二叉树的算法题(目前3道) 

    1. 相同的树(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/same-tree/

    题目:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

    如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

    示例1:

    输入:p = [1,2,3],q = [1,2,3]

    输出:true

    示例2:

    输入:p = [1,2],q = [1,null,2]

    输出:false

    示例3:

    输入:p = [1,2,1],q = [1,1,2]

    输出:false

    提示:

    • 两棵树上的节点数目都在范围 [0, 100] 内
    • -104 <= Node.val <= 104

    思路:

    代码:

    1. //判断两棵树是否相同
    2. public boolean isSameTree(TreeNode p, TreeNode q) {
    3. //如果p还有结点,q没有结点,证明两棵树的子树就不相同了,反之亦然
    4. if(p != null && q == null || p == null && q != null){
    5. return false;
    6. }
    7. //要么都为null,要么都不为null
    8. if(p == null && q == null){
    9. return true;
    10. }
    11. //如果两个结点的数值不一样就返回false
    12. if(p.val != q.val){
    13. return false;
    14. }
    15. return isSameTree(q.left,p.left) && isSameTree(q.right,p.right);
    16. }

    运行结果:

    2. 另一棵树的子树(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/subtree-of-another-tree/

    题目:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

    二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

    示例1:

    输入:root = [3,4,5,1,2],subRoot = [4,1,2]

    输出:true 

    示例2:

    输入:root = [3,4,5,1,2,null,null,null,null,0],subRoot = [4,1,2]

    输出:false

    提示:

    • root 树上的节点数量范围是 [1, 2000]
    • subRoot 树上的节点数量范围是 [1, 1000]
    • -104 <= root.val <= 104
    • -104 <= subRoot.val <= 104

    思路:

    代码:

    1. //判断两棵树是否相同
    2. public boolean isSameTree(TreeNode p, TreeNode q) {
    3. //如果p还有结点,q没有结点,证明两棵树的子树就不相同了,反之亦然
    4. if(p == null && q != null || p != null && q == null){
    5. return false;
    6. }
    7. //要么都为null,要么都不为null
    8. if(p == null && q == null){
    9. return true;
    10. }
    11. //如果两个结点的数值不一样就返回false
    12. if(p.val != q.val){
    13. return false;
    14. }
    15. return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
    16. }
    17. public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    18. if(root == null || subRoot == null){
    19. return false;
    20. }
    21. if(isSameTree(root,subRoot))//从当前结点开始
    22. return true;
    23. if(isSubtree(root.left,subRoot))//从左子树的根结点开始
    24. return true;
    25. if(isSubtree(root.right,subRoot))//从右子树的根结点开始
    26. return true;
    27. return false;
    28. }

    运行结果:

    3. 翻转二叉树(力扣)

    题目跳转链接icon-default.png?t=N7T8https://leetcode.cn/problems/invert-binary-tree/题目:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

    思路:

    (1)先将父亲结点交换

    (2)再将子树结点交换

    (3)然后递归下去

    代码:

    1. //翻转二叉树
    2. public TreeNode invertTree(TreeNode root) {
    3. //如果为空树,返回null
    4. if(root == null){
    5. return null;
    6. }
    7. //进行交换
    8. TreeNode tmp = root.left;
    9. root.left = root.right;
    10. root.right = tmp;
    11. invertTree(root.left);//翻转左子树
    12. invertTree(root.right);//翻转右子树
    13. return root;
    14. }

    运行结果:

  • 相关阅读:
    GB28181 sip和RTSP(Real-Time Streaming Protocol)实时流控制协议
    英语语法 — 时态
    建构居住安全生态,鹿客科技2023秋季发布会圆满举办
    Modbus通信协议介绍以及Modbus Poll、Slave软件使用介绍
    【Linux】环境基础开发工具使用(熟练使用必练)(学习复习兼顾)
    Firebird + FireDAC 界面操作的自增字段
    【数值计算方法】曲线拟合与插值:Lagrange插值、Newton插值及其python/C实现
    搞直播啦,千视超高清4K NDI编解码器8月3日19:00准时开播
    虽然webpack4以后。webpack 可以不用再引入一个配置文件来打包项目,但还是梳理常用配置信息
    [Vue3] pinia状态管理
  • 原文地址:https://blog.csdn.net/ANNE_fly/article/details/132777972