• 手撕代码(Simple)- Java后端高频面试算法题集锦 1


    1. 反转链表

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

    1. //方法一:迭代
    2. //将当前节点的next 指针改为指向前一个节点
    3. public ListNode reverseList(ListNode head) {
    4. ListNode prev = null;
    5. ListNode curr = head;
    6. while(curr != null) {
    7. ListNode next = curr.next;
    8. curr.next = prev;
    9. prev = curr;
    10. curr = next;
    11. }
    12. return prev;
    13. }
    14. //方法二:递归
    15. public ListNode reverseList1(ListNode head) {
    16. if(head == null || head.next == null) return head;
    17. ListNode newHead = reverseList1(head.next);
    18. head.next.next = head;
    19. head.next = null;
    20. return newHead;
    21. }

    2. 合并两个升序链表

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

    1. //方法一-迭代
    2. public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    3. ListNode list = new ListNode();
    4. ListNode p = list;
    5. while(list1 != null && list2 != null) {
    6. if(list1.val <= list2.val) { //list1小,list1合并、后移
    7. p.next = list1;
    8. list1 = list1.next;
    9. }
    10. else { //list2小,list2合并、后移
    11. p.next = list2;
    12. list2 = list2.next;
    13. }
    14. p = p.next; //合并list指针后移
    15. }
    16. //将剩余部分合并
    17. p.next = (list1 == null ? list2 : list1);
    18. return list;
    19. }
    20. //方法二-递归
    21. public ListNode mergeTwoLists1(ListNode list1, ListNode list2) {
    22. if(list1 == null)
    23. return list2;
    24. else if(list2 == null)
    25. return list1;
    26. else if(list1.val < list2.val) {
    27. list1.next = mergeTwoLists(list1.next, list2);
    28. return list1;
    29. }else {
    30. list2.next = mergeTwoLists(list1, list2.next);
    31. return list2;
    32. }
    33. }

    3. 判断是否环形链表

    给你一个链表的头节点 head ,判断链表中是否有环。

    1. //方法一:哈希表
    2. public boolean hasCycle(ListNode head) {
    3. Set seen = new HashSet();
    4. while(head != null) {
    5. if(!seen.add(head))
    6. return true;
    7. head = head.next;
    8. }
    9. return false;
    10. }
    11. //方法二:快慢指针
    12. //如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表
    13. public boolean hasCycle1(ListNode head) {
    14. if(head == null || head.next == null) return false;
    15. ListNode slow = head;
    16. ListNode fast = head.next;
    17. while(slow != fast) {
    18. if(fast == null || fast.next == null)
    19. return false;
    20. slow = slow.next;
    21. fast = fast.next.next;
    22. }
    23. return true;
    24. }

    4. 回文链表

    给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。

    1. //方法一:将链表复制到数组中后使用双指针法
    2. public boolean isPalindrome(ListNode head) {
    3. List vals = new ArrayList();
    4. //将链表的值复制到数组中
    5. ListNode cur = head;
    6. while(cur != null) {
    7. vals.add(cur.val);
    8. cur = cur.next;
    9. }
    10. //使用双指针判断是否回文
    11. int front = 0, back = vals.size() - 1;
    12. while(front < back) {
    13. if(!vals.get(front).equals(vals.get(back)))
    14. return false;
    15. front++;
    16. back--;
    17. }
    18. return true;
    19. }
    20. //方法二:递归
    21. private ListNode frontPointer; //递归函数外的指针
    22. public boolean isPalindrome1(ListNode head) {
    23. frontPointer = head;
    24. return recursivelyCheck(head);
    25. }
    26. private boolean recursivelyCheck(ListNode currentNode) {
    27. if(currentNode != null) {
    28. if(!recursivelyCheck(currentNode.next))
    29. return false;
    30. if(currentNode.val != frontPointer.val)
    31. return false;
    32. frontPointer = frontPointer.next;
    33. }
    34. return true;
    35. }
    36. //方法三:快慢指针
    37. /*
    38. * 1. 找到前半部分链表的尾节点。
    39. * 2. 反转后半部分链表。
    40. * 3. 判断是否回文。
    41. * 4. 恢复链表。
    42. * 5. 返回结果。
    43. */
    44. public boolean isPalindrome2(ListNode head) {
    45. if(head == null) return true;
    46. //找到前半部分链表的尾结点并翻转后半部分链表
    47. ListNode firstHalfEnd = endOfFirstHalf(head);
    48. ListNode secondHalfStart = reverseList(firstHalfEnd.next);
    49. //判断是否回文
    50. ListNode p1 = head;
    51. ListNode p2 = secondHalfStart;
    52. boolean result = true;
    53. while(result && p2 != null) {
    54. if(p1.val != p2.val)
    55. return false;
    56. p1 = p1.next;
    57. p2 = p2.next;
    58. }
    59. //还原链表并返回结果
    60. firstHalfEnd.next = reverseList(secondHalfStart);
    61. return result;
    62. }
    63. private ListNode endOfFirstHalf(ListNode head) {
    64. ListNode fast = head;
    65. ListNode slow = head;
    66. while(fast.next != null && fast.next.next != null) {
    67. fast = fast.next.next;
    68. slow = slow.next;
    69. }
    70. return slow;
    71. }
    72. private ListNode reverseList(ListNode head) {
    73. ListNode prev = null;
    74. ListNode curr = head;
    75. while(curr != null) {
    76. ListNode temp = curr.next;
    77. curr.next = prev;
    78. prev = curr;
    79. curr = temp;
    80. }
    81. return prev;
    82. }

    5. 回文数

    给你一个整数x,如果 x 是一个回文整数,返回 tru ;否则,返回 false 。

    回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

    • 例如,121 是回文,而 123 不是。

    1. public static boolean isPalindrome(int x) {
    2. if(x<0)
    3. return false;
    4. int rem,y=0;
    5. int quo=x;
    6. while(quo!=0){
    7. rem=quo%10;
    8. y=y*10+rem;
    9. quo=quo/10;
    10. }
    11. return y==x;
    12. }

    6. 合并二叉树

    给你两棵二叉树: root1 和 root2 ,返回合并后的二叉树。

    1. /*
    2. * 两个二叉树的对应节点可能存在以下三种情况,对于每种情况使用不同的合并方式。
    3. * 1. 如果两个二叉树的对应节点都为空,则合并后的二叉树的对应节点也为空;
    4. * 2.如果两个二叉树的对应节点只有一个为空,则合并后的二叉树的对应节点为其中的非空节点;
    5. * 3.如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应节点的值之和,此时需要显性合并两个节点。
    6. */
    7. //方法一:深度优先搜索
    8. public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
    9. if(root1 == null) return root2;
    10. if(root2 == null) return root1;
    11. TreeNode merged = new TreeNode(root1.val + root2.val);
    12. merged.left = mergeTrees(root1.left, root2.left);
    13. merged.right = mergeTrees(root1.right, root2.right);
    14. return merged;
    15. }

    7. 返回二叉树最大

    深给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    1. //方法一:深度优先遍历
    2. public int maxDepth(TreeNode root) {
    3. if(root == null)
    4. return 0;
    5. else {
    6. int left = maxDepth(root.left);
    7. int right = maxDepth(root.right);
    8. return Math.max(left, right) + 1;
    9. }
    10. }
    11. //方法二:广度优先遍历
    12. public int maxDepth1(TreeNode root) {
    13. if(root == null)
    14. return 0;
    15. Queue q = new LinkedList();
    16. q.offer(root);
    17. int ans = 0;
    18. while(!q.isEmpty()) {
    19. int size = q.size();
    20. while(size > 0) {
    21. TreeNode node = q.poll();
    22. if(node.left != null)
    23. q.offer(node.left);
    24. if(node.right != null)
    25. q.offer(node.right);
    26. size--;
    27. }
    28. ans++;
    29. }
    30. return ans;
    31. }

    8. 翻转二叉树

    给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

    1. //递归
    2. public TreeNode invertTree(TreeNode root) {
    3. if(root == null)
    4. return null;
    5. TreeNode left = invertTree(root.left);
    6. TreeNode right = invertTree(root.right);
    7. root.left = right;
    8. root.right = left;
    9. return root;
    10. }

    9. 二叉树中序遍历

    1. //二叉树的中序遍历
    2. public class InorderTraversal {
    3. //方法一:递归
    4. public List inorderTraversal(TreeNode root) {
    5. List res = new ArrayList();
    6. inorder(root, res);
    7. return res;
    8. }
    9. public void inorder(TreeNode root, List res) {
    10. if(root == null) return;
    11. inorder(root.left, res);
    12. res.add(root.val);
    13. inorder(root.right, res);
    14. }
    15. //方法二:迭代
    16. public List inorderTraversal1(TreeNode root){
    17. List res = new ArrayList();
    18. Deque stk = new LinkedList();
    19. while(root != null || !stk.isEmpty()) {
    20. while(root != null) {
    21. stk.push(root);
    22. root = root.left;
    23. }
    24. root = stk.pop();
    25. res.add(root.val);
    26. root = root.right;
    27. }
    28. return res;
    29. }
    30. //方法三:Morris遍历算法
    31. public List inorderTraversal2(TreeNode root){
    32. List res = new ArrayList();
    33. TreeNode predecessor = null;
    34. while(root != null) {
    35. //predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
    36. if(root.left != null) {
    37. predecessor = root.left;
    38. while(predecessor.right != null && predecessor.right != root)
    39. predecessor = predecessor.right;
    40. // 让 predecessor 的右指针指向 root,继续遍历左子树
    41. if(predecessor.right == null) {
    42. predecessor.right = root;
    43. root = root.left;
    44. // 说明左子树已经访问完了,我们需要断开链接
    45. }else {
    46. res.add(root.val);
    47. predecessor.right = null;
    48. root = root.right;
    49. }
    50. }else { // 如果没有左孩子,则直接访问右孩子
    51. res.add(root.val);
    52. root = root.right;
    53. }
    54. }
    55. return res;
    56. }
    57. }
    58. class TreeNode {
    59. int val;
    60. TreeNode left;
    61. TreeNode right;
    62. TreeNode() {}
    63. TreeNode(int val) { this.val = val; }
    64. TreeNode(int val, TreeNode left, TreeNode right) {
    65. this.val = val;
    66. this.left = left;
    67. this.right = right;
    68. }
    69. }

    10. 爬楼梯

    假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

    每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

    1. //动态规划-f(x)=f(x−1)+f(x−2)
    2. public int climbStairs(int n) {
    3. int p = 0, q = 0, r = 1;
    4. for(int i = 1; i <= n; i++) {
    5. p = q;
    6. q = r;
    7. r = p + q;
    8. }
    9. return r;
    10. }

    11. 有效括号

    给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

    有效字符串需满足:

    • 左括号必须用相同类型的右括号闭合。
    • 左括号必须以正确的顺序闭合。
    • 每个右括号都有一个对应的相同类型的左括号。
    1. //方法一
    2. public boolean isValid(String s) {
    3. int n = s.length();
    4. if(n % 2 == 1) return false; //如果字符长度为奇数,则无法匹配
    5. Map map = new HashMap(){{
    6. put(')', '(');
    7. put(']', '[');
    8. put('}', '{');
    9. }};
    10. Deque stack = new LinkedList();
    11. for(int i = 0; i < n; i++) {
    12. char ch = s.charAt(i); //依次取字符串中的括号
    13. if(map.containsKey(ch)) { //是否为右括号
    14. if(stack.isEmpty() || stack.peek() != map.get(ch)) //如果栈为空,或者栈中剩余的括号不匹配,则返回false
    15. return false;
    16. stack.pop(); //匹配完成,则弹出
    17. }else {
    18. stack.push(ch); //将左括号入栈
    19. }
    20. }
    21. return stack.isEmpty(); //判断栈中的括号是否匹配完毕
    22. }
    23. //方法二
    24. public boolean isValid1(String s) {
    25. Stackstack = new Stack();
    26. for(char c: s.toCharArray()){
    27. if(c=='(')
    28. stack.push(')');
    29. else if(c=='[')
    30. stack.push(']');
    31. else if(c=='{')
    32. stack.push('}');
    33. else if(stack.isEmpty() || c != stack.pop())
    34. return false;
    35. }
    36. return stack.isEmpty();
    37. }

    12. 比特位计数

    给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

    1. //方法一:使用内置函数Integer.bitCount()
    2. public static int[] countBits(int n) {
    3. int[] ans = new int[n + 1];
    4. for(int i = 0; i <= n; ++i) {
    5. int count = Integer.bitCount(i);
    6. ans[i] = count;
    7. }
    8. return ans;
    9. }
    10. //方法二:Brian Kernighan算法
    11. //对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。
    12. //因此,对 x 重复该操作,直到 x 变成 0,则操作次数即为 x 的「一比特数」。
    13. public int[] countBits1(int n) {
    14. int[] bits = new int[n + 1];
    15. for(int i = 0; i <= n; ++i) {
    16. bits[i] = countOnes(i);
    17. }
    18. return bits;
    19. }
    20. private int countOnes(int x) {
    21. int ones = 0;
    22. while(x > 0) {
    23. x &= (x - 1);
    24. ones++;
    25. }
    26. return ones;
    27. }
    28. //方法三:动态规划-最高有效位
    29. public int[] countBits2(int n) {
    30. int[] bits = new int[n + 1];
    31. int highBit = 0;
    32. for(int i = 1; i <= n; ++i) {
    33. if((i & (i - 1)) == 0)
    34. highBit = i;
    35. bits[i] = bits[i - highBit] + 1;
    36. }
    37. return bits;
    38. }
    39. //方法四:动态规划-最低有效位
    40. public int[] countBits3(int n) {
    41. int[] bits = new int[n + 1];
    42. for(int i = 1; i <= n; ++i)
    43. bits[i] = bits[i >> 1] + (i & 1);
    44. return bits;
    45. }
    46. //方法五:动态规划-最低设置位
    47. public int[] countBits4(int n) {
    48. int[] bits = new int[n + 1];
    49. for(int i = 1; i <= n; ++i)
    50. bits[i] = bits[i & (i - 1)] + 1;
    51. return bits;
    52. }

    来源:力扣(LeetCode)
    链接:题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台

  • 相关阅读:
    分水果(冬季每日一题 21)
    java中的函数式接口是什么?
    系统优化脚本支持Ubuntu和CentOS
    在vite项目中添加Cesium,我们如何让它完美运行?
    mybatis
    ABeam Team Building |「TDC Green Day——夏日Go Green·畅享山海间 环保公益行动」回顾
    经济数据预测 | Python实现机器学习(MLP、XGBoost)金融市场预测
    电力智能化运维平台:提高效率和保障电力系统的稳定运行
    Go语言入门篇
    官宣!2024影响因子即将公布,或将迎来这些重大变化!
  • 原文地址:https://blog.csdn.net/weixin_49851451/article/details/127107615