常见算法题整理

时间:2025-01-23 13:56:17

算法题:

  • 数据结构:数组、链表、字符串、树、数学、栈、hash表、图
  • 动态规划、中心扩散、回溯算法、递归、迭代、贪心算法、
  • 从整体到细节,自顶向下,从抽象到具体的框架思维是通用的,不只是学习数据结构和算法,学习其他任何知识都是高效的。

一、数组:

技巧:

  • 双指针

二、链表:

技巧:

  • 链表问题一般先定义一个哑结点,将特殊清除处理掉

链表反转

private  Node reverse(Node node) {
        Node pre = null, cur = node, tem = null;
        while (cur != null) {
            tem = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tem;
        }
        return pre;
    }

2. 两数相加

难度中等 6174

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) {  = val; }
 *     ListNode(int val, ListNode next) {  = val;  = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 特判:
        if(l1==null)return l2;
        if(l2==null)return l1;

        int carry=0;
        ListNode dummy =new ListNode(0);// 定义个哑结点,便于处理
        ListNode pre=dummy; // 指向哑结点的地址、最终返回;

        while(l1!=null && l2!=null){
            int sum =l1.val+l2.val+carry;
            carry =sum/10;
            ListNode node =new ListNode(sum%10);
            dummy.next=node;
            l1=l1.next;
            l2=l2.next;
            dummy=dummy.next;
        }
        while(l1!=null){
            int sum =l1.val+carry;
            carry =sum/10;
            ListNode node =new ListNode(sum%10);
            dummy.next=node;
            l1=l1.next;
            dummy=dummy.next;
        }
       while(l2!=null){
            int sum =l2.val+carry;
            carry =sum/10;
            ListNode node =new ListNode(sum%10);
            dummy.next=node;
            l2=l2.next;
            dummy=dummy.next;
        }
        if(carry>0){
            dummy.next=new ListNode(carry); 
        }
        return pre.next;
    }
}
  • 看清题意,有时候需要做链表的反转。

19. 删除链表的倒数第 N 个结点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) {  = val; }
 *     ListNode(int val, ListNode next) {  = val;  = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 方式1: 遍历一遍,统计元素个数总和 ,再遍历
        // 方式2: 快慢指针

        // 定义一个哑结点,使得链表中所有的结点都有前驱结点
        ListNode dummy = new ListNode(0,head);
        // 定义快慢指针都指向哑结点
        ListNode fast=dummy, slow=dummy;
        // 让快指针先往前移动 n+1 步
        for(int i=0;i<n+1;i++){
            fast=fast.next;
        }
        // 同时移动快慢指针,直到快指针指向null
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        // 不清理要删除元素所占的空间。
        // =;
        // 清理待删除元素所占的空间
        ListNode delNode =slow.next;
        slow.next=delNode.next;
        delNode.next=null;
        
        return dummy.next;
    }
}

寻找链表的中间结点:

// 快慢指针,慢指针一次走一步,快指针一次走两步。最终慢指针指向就为中间结点
private ListNode endOfFirstHalf(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

141. 环形链表

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!(head)) {
                return true;
            }
            head = ;
        }
        return false;
    }
}

面试题 02.06. 回文链表

  • 其中包括获取中间结点的方法
  • 链表反转的方法。
  • 最终达到
class Solution {
    public boolean isPalindrome(ListNode head) {
// 方式3:反转后半段链表,然后比较前后半段链表的值
        // 1、通过快慢指针,找出链表的中间结点,慢指针的next结点为后半部分的开始
        ListNode firstTail = getFirstHalf(head);
        if(firstTail==null) return true ;
        //2、反转后半部分链表
        ListNode nextHalf= reversal(firstTail.next);
        //3、比较将原链表和后半部分值比较。(不同return false, 相同返回true)
        while(nextHalf!=null){
            if(head.val!=nextHalf.val){
                return false;
            } else{
                head=head.next;
                nextHalf=nextHalf.next;
            }
        }
        return true;
    }
    // 获取链表中间结点的方法
    private ListNode getFirstHalf(ListNode head){
        ListNode fast =head,slow =head;
        while(fast!=null&&fast.next!=null&& fast.next.next!=null){
            fast =fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
    // 链表反转
    private ListNode reversal(ListNode node){
        ListNode pre = null, cur=node,tem=null;
        while(cur!=null){
            tem =cur.next;
            cur.next=pre;
            pre=cur;
            cur=tem;
        } 
        return pre;
    }
}

三、字符串:

  • 字符串问题一般转换成char数组,去操作数组,省的每次操作字符串的(i)

1、验证子串是否为回文串

// char[] charArr =();// 字符串转换成char数组

private static boolean validateSubstring(char[] charArr, int left ,int right){
  while(left<right){
    if(charArr[left]!=charArr[right]){
      return false;
    }
    left++;
    right--;
  }
  return true;
}

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

示例 2:

输入:s = “cbbd”
输出:“bb”
示例 3:

输入:s = “a”
输出:“a”
示例 4:

输入:s = “ac”
输出:“a”

提示:

1 <= <= 1000
s 仅由数字和英文字母(大写和/或小写)组成

class Solution {
    public String longestPalindrome(String s) {
        // 特判
        if(null==s || s.length()<2) return s;
        // 中心扩散法
        int res =1; //最大长度
        int ll=0;   //最大回文串的左指针
        int rr=0;   //最大回文串的右指针
        //将字符串转成char数组,不在循环中去使用(i)
        char[] chArr =s.toCharArray();

        // 开始遍历char数组
        for(int i =0; i<chArr.length; i++){
            // 以i为中心向两边扩散,寻找最长子串(通俗:回文串为奇数长度i为中心)
            int l =i-1;
            int r =i+1;

            while(l>=0 && r<chArr.length && chArr[l]==chArr[r]){
                int len =r-l+1;
                if(len>res){
                    res=len;
                    ll=l;
                    rr=r;
                }
                l--;
                r++;
            }
            // 以i为左指针,i+1为右指针(通俗:回文串为偶数长度)
            l=i;
            r=i+1;
            while(l>=0 && r<chArr.length && chArr[l]==chArr[r]){
                int len =r-l+1;
                if(len>res){
                    res=len;
                    ll=l;
                    rr=r;
                }
                l--;
                r++;
            }
        } 
        return s.substring(ll,rr+1);
    }
}

四、树:

技巧:

  • 递归最简单,一般树的问题都可以通过递归来解决。(需要再学下迭代,迭代是显示维护一个栈)
  • 前序遍历:根左右;中序遍历:左根右;后序遍历:左右根。

94. 二叉树的中序遍历

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) {  = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *          = val;
 *          = left;
 *          = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        // 中序遍历:左 根 右
        List<Integer> list = new ArrayList<Integer>();
        leftOrder(root,list);
        return list;
    }
    private void leftOrder(TreeNode node, List list){
        if(null==node) return;

        leftOrder(node.left,list);
        list.add(node.val);
        leftOrder(node.right,list);

    }
}

五、栈:

六、Hash表

七、图

八、动态规划(labuladong)

动态规划的特点:

  • 重叠子问题
  • 状态转义方程(最关键)
  • 最优子结构

题型:

  • 求最值
  • 核心:穷举:不要小看暴力穷举

解题套路:

  • 明确【状态】
  • 明确【选择】
  • 明确dp函数、数组的定义
  • 明确base case。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsFw2FXi-1622290180636)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqdphKC2-1622290180639)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lMB5LyEx-1622290180641)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iv9SXKeI-1622290180645)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onKZYvFS-1622290180646)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RaL0PV6-1622290180648)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DuZMyq2H-1622290180649)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lSizQQ5a-1622290180649)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

  • 空间换时间。引入数组备忘录,占用数组大小的空间。

  • 自底向上:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EtPHrtHP-1622290180650)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXdBtZBI-1622290180651)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tdIKRMnf-1622290180652)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

  • 优秀的算法都是慢慢演变来的 ,就像系统架构一样,不是一开始就很优,都是慢慢演变的

509:斐波那契数列

class Solution {
    public int fib(int n) {
        // 方式1:直接递归 ,但是时间复杂度较高,多了很多重复的计算。
         /*// 方式2:带备忘录的递归。将所有计算过的值缓存下来,递归中直接返回。(自顶向下推,然后自底向上回溯)
       // 初始化备忘录。
         int[] notes =new int[n+1];
        // 进行带备忘录的递归。
        return helper(notes, n);*/

       /* // 方式3:dp数组迭代法 (自底向上)
        if(n==0) return 0;
        int[] dp =new int[n+1];
        // base case 
        dp[0]=0; dp[1]=1;
        // 状态转移
        for(int i=2; i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];*/
        //方式4: 优化空间复杂度,求第n个数,其实只需要知道他的前两个数就可以了 ,
        // 因此可以定义两个变量去表示它的前两个数的值,依次来优化空间复杂度
        if(n==0) return 0;
        int pre=0,curr=1;
        for(int i=2;i<=n;i++){
            int sum=pre+curr;
            pre =curr;
            curr=sum;
        }
        return curr;
    }
    private int helper(int[] notes,int n){
        if(n==0|| n==1) return n; //base case
        if(notes[n]!=0) return notes[n];
        notes[n] =helper(notes,n-1)+helper(notes,n-2);
        return notes[n];
    }
}

322. 零钱兑换

  • 动态规划 是用于求最值的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8LPzpXZ-1622290180652)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-inO9cJ24-1622290180653)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

  • 自顶向下的递归(带备忘录)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDrON8qK-1622290180654)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfYzUeuZ-1622290180655)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-byRez0zV-1622290180655)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4xp2AyzG-1622290180656)(%E5%B8%B8%E8%A7%81%E7%AE%97%E6%B3%95%E9%A2%/)]

动态规划问题的本质:

  • 1、如何穷举
    • 写出状态转移方程,暴力穷举所有可行解。
  • 2、如何聪明的穷举
    • 用备忘录消除重叠子问题,写出自动向下解法
    • 进一步,可以写出自动向下解法
    • 再进一步,可能可以优化空间复杂度