算法题笔记

130 阅读25分钟

文献 哈希表与字符串 > 链表 > 二叉树与图 > 二分查找与二叉排序树 > 栈、队列、堆 > 其他(主要是一些数学问题)> 递归、回溯与分治 & 贪心算法 > 搜索 > 复杂数据结构 > 动态规划

一、哈希表与字符串

最长回文串(409)

题目:

给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的 回文串 的长度。

在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。

示例 1:

输入: s = "abccccdd"
输出: 7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

示例 2:

输入: s = "a"
输出: 1
解释: 可以构造的最长回文串是"a",它的长度是 1。

提示:

  • 1 <= s.length <= 2000
  • s 只由小写 和/或 大写英文字母组成

算法思路:

  1. 按照字符计算出,每个字符出现过多少次。
  2. 字符出现偶数次,必然是回文字符串,直接相加。
  3. 字符出现奇数次,相加偶数部分,奇数部分只能加一次。

java代码:

class Solution {
    public int longestPalindrome(String s) {
        Integer result = 0;
        //计算字符出现次数
        Map<Character, Integer> map = recordCounter(s);

        for (Integer value : map.values()) {
            //计算偶数出现次数
            result += value / 2 * 2;
            if (value % 2 == 1 && result % 2 == 0) {
                //当字符出现奇数次时进入一次,让结果存在回文中心
                result += 1;
            }
        }
        return result;
    }

    private Map<Character, Integer> recordCounter(String s) {
        int len = s.length();
        Map<Character, Integer> map = new HashMap(len);
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (map.containsKey(c)) {
                map.put(c, map.get(c) + 1);
            } else {
                map.put(c, 1);
            }
        }
        return map;
    }
}

单词规律(290)

题目:

给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s ****中的每个非空单词之间存在着双向连接的对应规律。

示例1:

输入: pattern = "abba", s = "dog cat cat dog"
输出: true

示例 2:

输入: pattern = "abba", s = "dog cat cat fish"
输出: false

示例 3:

输入: pattern = "aaaa", s = "dog cat cat dog"
输出: false

提示:

  • 1 <= pattern.length <= 300
  • pattern 只包含小写英文字母
  • 1 <= s.length <= 3000
  • s 只包含小写英文字母和 ' '
  • s 不包含 任何前导或尾随对空格
  • s 中每个单词都被 单个空格 分隔

算法思路:

  1. 定义一个map保证pattern中的每一个字符都对应s中的每一个单词。
  2. 定义一个map保证s中的每一个单词都对应pattern中的每一个字符都。

java代码:

class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character, String> map = new HashMap<>();
        Map<String, Character> map1 = new HashMap<>();
        String[] strArry = s.split(" ");
        Integer pLen = pattern.length();
        if (strArry.length != pLen) {
            return false;
        }
        for (int i = 0; i < pLen; i++) {
            // 通过pattern找s,保证相同的pattern能找到相同的s
            if (map.containsKey(pattern.charAt(i))) {
                if (!Objects.equals(map.get(pattern.charAt(i)), strArry[i])) {
                    return false;
                }
            } else {
                map.put(pattern.charAt(i), strArry[i]);
            }
            // 通过s找pattern,保证相同的s能找到相同的pattern
            if (map1.containsKey(strArry[i])) {
                if (!Objects.equals(map1.get(strArry[i]), pattern.charAt(i))) {
                    return false;
                }
            } else {
                map1.put(strArry[i], pattern.charAt(i));
            }
        }

        return true;
    }

}

同字符词语分组(49)

题目:

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

算法思路:

  1. 对每一个单词进行字符串排序,将排序后的字符串作为group的依据进行分类

java代码:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        int len = strs.length;
        Map<String, List<String>> map = new HashMap<>(strs.length);
        for (int i = 0; i < len; i++) {
            String sorted = sortedString(strs[i]);
            if (map.containsKey(sorted)) {
                map.get(sorted).add(strs[i]);
            } else {
                List<String> list = new ArrayList<>();
                list.add(strs[i]);
                map.put(sorted, list);
            }
        }
        return map.values()
                .stream()
                .collect(Collectors.toList());
    }

    private String sortedString(String source) {
        char[] chars = source.toCharArray();

        // 使用 Arrays.sort() 对字符数组进行排序
        Arrays.sort(chars);

        // 将排序后的字符数组转换回字符串
        return new String(chars);
    }
}

无重复字符的最长子串(3)

题目:

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 ****的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

算法思路:

  1. 滑动窗口

java代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int start = 0;
        int end = 0;
        int max = 0;
        int len = s.length();
        while (start < len && end < len) {
            end = start;
            Map<Character, Integer> map = new HashMap<>();
            for (; end < len; end++) {
                if (map.containsKey(s.charAt(end))) {
                    break;
                } else {
                    map.put(s.charAt(end), end);
                }
            }
            int temp = end - start;
            if (max < temp) {
                max = temp;
            }
            if (end < len) {
                start = map.get(s.charAt(end)) + 1;
            }

        }
        return max;
    }
} 

重复的DNA序列(187)

题目:

DNA序列 由一系列核苷酸组成,缩写为 'A''C''G' 和 'T'.。

  • 例如,"ACGAATTCCG" 是一个 DNA序列 。

在研究 DNA 时,识别 DNA 中的重复序列非常有用。

给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。

示例 1:

输入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出: ["AAAAACCCCC","CCCCCAAAAA"]

示例 2:

输入: s = "AAAAAAAAAAAAA"
输出: ["AAAAAAAAAA"]

提示:

  • 0 <= s.length <= 105
  • s[i]``==``'A''C''G' or 'T'

算法思路:

1.将整个dna序列按照10为长度切割成多块 2.用map记录每一块出现的次数,当出现次数为2时,则添加结果

java代码:

class Solution {
    static final int T = 10;
    public List<String> findRepeatedDnaSequences(String s) {
        Map<String,Integer> dnaRecordMap = new HashMap<>();
        List<String> result = new LinkedList<>();
        for(int i =0;i<=s.length()-T;i++){
            String dna = s.substring(i,i+T);
            dnaRecordMap.put(dna,dnaRecordMap.getOrDefault(dna,0)+1);
            if(dnaRecordMap.get(dna) == 2){
                result.add(dna);
            }
        }
        return result;
    }
}

最小窗口子串(76)

题目:

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
解释: 最小覆盖子串 "BANC" 包含来自字符串 t 的 'A''B''C'

示例 2:

输入: s = "a", t = "a"
输出: "a"
解释: 整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • s 和 t 由英文字母组成

 

进阶: 你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

算法思路:

动态窗口

java代码:

class Solution {
    public String minWindow(String s, String t) {
        if (s == null || s.isEmpty() || t == null || t.isEmpty() || s.length() < t.length())
            return "";

        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();

        // 初始化 need,记录 t 中每个字符的出现次数
        for (char c : t.toCharArray())
            need.put(c, need.getOrDefault(c, 0) + 1);

        int left = 0, right = 0; // 窗口的左右边界
        int valid = 0; // 已经匹配上的字符数量
        int start = 0, minLen = Integer.MAX_VALUE; // 最小窗口的起始位置和长度

        while (right < s.length()) {
            char r = s.charAt(right);
            right++;

            // 更新窗口内字符的计数
            if (need.containsKey(r)) {
                window.put(r, window.getOrDefault(r, 0) + 1);
                if (window.get(r).equals(need.get(r)))
                    valid++;
            }

            // 当窗口内的字符已经完全包含了 t 中的所有字符时
            while (valid == need.size()) {
                // 更新最小窗口的起始位置和长度
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }

                char l = s.charAt(left);
                // 缩小窗口,移动左边界
                if (need.containsKey(l)) {
                    window.put(l, window.get(l) - 1);
                    if (window.get(l) < need.get(l))
                        valid--;
                }
                left++;
            }
        }
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }
}

JZ5 替换空格

题目:

描述

请实现一个函数,将一个字符串s中的每个空格替换成“%20”。

例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

数据范围:0≤len(s)≤1000 0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。

示例1

输入:

"We Are Happy"

返回值:

"We%20Are%20Happy"

示例2

输入:

" "

返回值:

"%20"

算法思路:

StringBuilder聚合

java代码:

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @return string字符串
     */
    public String replaceSpace (String s) {
       StringBuilder stringBuilder = new StringBuilder();
       for(int i =0;i<s.length();i++){
            if(s.charAt(i) == ' '){
                stringBuilder.append("%20");
            }else{
                stringBuilder.append(s.charAt(i));
            }
       }
       return stringBuilder.toString();
    }
}

二、链表

K个一组翻转链表(25)

题目:

给你链表的头节点 head ,每 k **个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k **的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

  示例 1:

输入: head = [1,2,3,4,5], k = 2
输出: [2,1,4,3,5]

示例 2:

输入: head = [1,2,3,4,5], k = 3
输出: [3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶: 你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

算法思路:

  1. 分组进行链表反转
  2. 衔接每一个反转后的结果

java代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode pre = dummy;
        ListNode end = dummy;

        while (end.next != null) {
            for (int i = 0; i < k && end != null; i++)
                end = end.next;
            if (end == null)
                break;
            ListNode start = pre.next;
            ListNode next = end.next;
            end.next = null;/*  */
            pre.next = reverse(start);
            start.next = next;
            pre = start;

            end = pre;
        }
        return dummy.next;
    }

    private ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }

}

随机链表的复制(138)

题目:

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

示例 1:

输入: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出: [[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入: head = [[1,1],[2,1]]
输出: [[1,1],[2,1]]

示例 3:

输入: head = [[3,null],[3,0],[3,null]]
输出: [[3,null],[3,0],[3,null]]

else { if (curBig == null) { curBig = cur; bigHead = cur; } else { curBig.next = cur; curBig = cur; } } 提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.random 为 null 或指向链表中的节点。

算法思路:

链表复制必要元素:头节点、动态节点

java代码:

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        Node oriCur = head;
        Node copyRoot = new Node(0);
        Node copyCur = copyRoot;
        Map<Node, Node> map = new HashMap<>();
        while (oriCur != null) {
            Node copyNext = new Node(oriCur.val);
            map.put(oriCur, copyNext);
            copyCur.next = copyNext;
            oriCur = oriCur.next;
            copyCur = copyCur.next;
        }

        copyCur = copyRoot.next;
        oriCur = head;
        while (oriCur != null) {
            copyCur.random = map.get(oriCur.random);
            oriCur = oriCur.next;
            copyCur = copyCur.next;
        }

        return copyRoot.next;

    }
}

86. 分隔链表

题目:

给你一个链表的头节点 head 和一个特定值 **x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:

输入: head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

示例 2:

输入: head = [2,1], x = 2
输出:[1,2]

提示:

  • 链表中节点的数目在范围 [0, 200] 内
  • -100 <= Node.val <= 100
  • -200 <= x <= 200

算法思路:

首先定于头节点,然后定义动态节点,串联拼接。

java代码:

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode small = new ListNode(0);
        ListNode smallHead = small;
        ListNode large = new ListNode(0);
        ListNode largeHead = large;
        while (head != null) {
            if (head.val < x) {
                small.next = head;
                small = small.next;
            } else {
                large.next = head;
                large = large.next;
            }
            head = head.next;
        }
        large.next = null;
        small.next = largeHead.next;
        return smallHead.next;
    }
}

三、二叉树与图

113. 路径总和 II

题目:

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出: [[5,4,11,2],[5,8,4,5]]

示例 2:

输入: root = [1,2,3], targetSum = 5
输出: []

示例 3:

输入: root = [1,2], targetSum = 0
输出: []

提示:

  • 树中节点总数在范围 [0, 5000] 内
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

算法思路:

depthFirstSearch 深度优先搜索

  1. 定义记录数组LinkedList.offerLast(a);pollLast()
  2. 函数返回为空的规则
  3. 获取满足要求的规则

java代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    private List<List<Integer>> result = new LinkedList<>();
    private Deque<Integer> path = new LinkedList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        dfs(root, targetSum);
        return result;
    }

    private void dfs(TreeNode root, int targetSum) {
        if (root == null) {
            return;
        }
        path.offerLast(root.val);
        targetSum = targetSum - root.val;
        if (root.left == null && root.right == null && targetSum == 0) {
            result.add(new LinkedList<>(path));
        }
        dfs(root.left,targetSum);
        dfs(root.right,targetSum);
        path.pollLast();
    }

}

236. 二叉树的最近公共祖先

题目:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

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

提示:

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同 。
  • p != q
  • p 和 q 均存在于给定的二叉树中。

算法思路:

depthFirstSearch 深度优先搜索

  1. 终止条件
  2. 递归逻辑
  3. 返回值

java代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return dfs(root, p, q);
    }

    private TreeNode dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q)
            return root;
        TreeNode left = dfs(root.left, p, q);
        TreeNode right = dfs(root.right, p, q);
        if (left == null)
            return right;
        if (right == null)
            return left;
        return root;
    }
}

114. 二叉树展开为链表

题目:

给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:

输入: root = [1,2,5,3,4,null,6]
输出: [1,null,2,null,3,null,4,null,5,null,6]

示例 2:

输入: root = []
输出: []

示例 3:

输入: root = [0]
输出: [0]

提示:

  • 树中结点数在范围 [0, 2000] 内
  • -100 <= Node.val <= 100

进阶: 你可以使用原地算法(O(1) 额外空间)展开这棵树吗?

算法思路:

dfs 深度优先搜索

java代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
        dfs(root);
    }

    public TreeNode dfs(TreeNode root) {
        if (root == null || (root.left == null && root.right == null)) {
            return root;
        }
        TreeNode left = dfs(root.left);
        TreeNode right = dfs(root.right);
        if (left != null && right != null) {
            left.right = root.right;
            root.right = root.left;
            root.left = null;
            return right;
        }
        if (left != null) {
            root.right = root.left;
            root.left = null;
            return left;
        }
        return right;
    }
}

207. 课程表

题目:

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例 1:

输入: numCourses = 2, prerequisites = [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

示例 2:

输入: numCourses = 2, prerequisites = [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • prerequisites[i] 中的所有课程对 互不相同

算法思路:

java代码:

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] ans = new int[numCourses];//存每个结点的入度
        List<List<Integer>> res = new ArrayList<>();//存结点之间依赖关系
        Queue<Integer> queue = new LinkedList<>();
        //初始化二维List集合
        for(int i = 0; i < numCourses; i++)
            res.add(new ArrayList<>());
        //取出每一对结点
        for(int[] temp : prerequisites){
            ans[temp[0]]++;
            res.get(temp[1]).add(temp[0]);
        }
        
        //先把所有入度为0的结点加入队列
        for(int i = 0; i < numCourses; i++)
            if(ans[i] == 0) 
                queue.add(i);

        while(!queue.isEmpty()){
            int pre = queue.poll();
            numCourses--;
            //根据依赖关系,把入度-1
            for(int cur : res.get(pre)){
                if(--ans[cur] == 0)
                    queue.add(cur);
            }
        }
        return numCourses == 0;
    }
}

四、二分查找与二叉排序树

35. 搜索插入位置

题目:

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 为 无重复元素 的 升序 排列数组
  • -104 <= target <= 104

算法思路:

java代码:

class Solution {
    public int searchInsert(int[] nums, int target) {
        return find(nums, 0, nums.length - 1, target);
    }

    public int find(int[] nums, int left, int right, int target) {
        if (right - left < 0) {
            return left;
        }
        int mid = (right + left) / 2;
        if (nums[mid] < target) {
            return find(nums, mid + 1, right, target);
        } else {
            return find(nums, left, mid - 1, target);
        }
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置

题目:

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

示例 3:

输入: nums = [], target = 0
输出: [-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

算法思路:

java代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int first = -1;
        int end = -1;

        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                first = mid;
                right = mid - 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        left = 0;
        right = nums.length - 1;

        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                end = mid;
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return new int[] { first, end };

    }

}

五、栈、队列、堆

225. 用队列实现栈

题目:

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

实现 MyStack 类:

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

注意:

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

  示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:

  • 1 <= x <= 9
  • 最多调用100 次 pushpoptop 和 empty
  • 每次调用 pop 和 top 都保证栈不为空

进阶: 你能否仅用一个队列来实现栈。

算法思路:

java代码:

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    /** Initialize your data structure here. */
    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue1.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return queue1.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue1.isEmpty();
    }
}

232. 用栈实现队列

题目:

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(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:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示:

  • 1 <= x <= 9

  • 最多调用 100 次 pushpoppeek 和 empty

  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作   进阶:

  • 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

算法思路:

java代码:

class MyQueue {
    Deque<Integer> inStack;
    Deque<Integer> outStack;

    public MyQueue() {
        inStack = new ArrayDeque<Integer>();
        outStack = new ArrayDeque<Integer>();
    }

    public void push(int x) {
        inStack.push(x);
    }

    public int pop() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.pop();
    }

    public int peek() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.peek();
    }

    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    private void in2out() {
        while (!inStack.isEmpty()) {
            outStack.push(inStack.pop());
        }
    }
}

155. 最小栈

题目:

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

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

示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示:

  • -231 <= val <= 231 - 1
  • poptop 和 getMin 操作总是在 非空栈 上调用
  • pushpoptop, and getMin最多被调用 3 * 104 次

算法思路:

用栈可以保存最小值minStack.push(Math.min(minStack.peek(), x));

java代码:

class MinStack {
    private List<Integer> list;

    public MinStack() {
        this.list = new ArrayList<>();
    }

    public void push(int val) {
        list.add(val);
    }

    public void pop() {
        if (list.size() > 0)
            list.remove(list.size() - 1);
    }

    public int top() {
        return list.get(list.size() - 1);
    }

    public int getMin() {
        return Collections.min(list);
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

其他算法

190. 颠倒二进制位

题目:

颠倒给定的 32 位无符号整数的二进制位。

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。   示例 1:
输入: n = 00000010100101000001111010011100
输出: 964176192 (00111001011110000010100101000000)
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596 , 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。

示例 2:

输入: n = 11111111111111111111111111111101
输出: 3221225471 (10111111111111111111111111111111)
解释: 输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
     因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。 

算法思路:

www.cnblogs.com/sunny3158/p…

java代码:

public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        int res = 0;
        for (int i = 0; i < 32 && n != 0; i++) {
            res = res | ((n & 1) << (31 - i));
            n = n >>> 1;
        }
        return res;
    }
}

1206.设计跳表

题目:

不使用任何库函数,设计一个 跳表 。

跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。

例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 8045 到跳表中,以下图的方式操作:

跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)

了解更多 : oi-wiki.org/ds/skiplist…

在本题中,你的设计应该要包含这些函数:

  • bool search(int target) : 返回target是否存在于跳表中。
  • void add(int num): 插入一个元素到跳表。
  • bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。

注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

算法思路:

java代码:

链表

无重复字符的最长子串(3)

题目:

算法思路:

java代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Skiplist {
    private Node<Integer> head;
    private static final int MAX_LEVEL = 4;
    private final Random random = new Random();

    public Skiplist() {
        this.head = new Node<>(null, MAX_LEVEL);
    }

    public boolean search(int target) {
        Node<Integer> current = head;
        for (int i = MAX_LEVEL - 1; i >= 0; i--) {
            while (current.getForword().size() > i && current.getForword().get(i) != null
                    && current.getForword().get(i).getValue() < target) {
                current = current.getForword().get(i);
            }
        }
        if (current.getForword().size() > 0) {
            current = current.getForword().get(0);
        }
        return current != null && current.getValue() != null && current.getValue() == target;
    }

    public void add(int num) {
        Node<Integer> current = head;
        Node<Integer>[] update = new Node[MAX_LEVEL];
        for (int i = MAX_LEVEL - 1; i >= 0; i--) {
            while (current.getForword().size() > i && current.getForword().get(i) != null
                    && current.getForword().get(i).getValue() < num) {
                current = current.getForword().get(i);
            }
            update[i] = current;
        }

        int level = randomLevel();
        Node<Integer> newNode = new Node<>(num, level);
        for (int i = 0; i < level; i++) {
                newNode.getForword().add(update[i].getForword().get(i));
                update[i].getForword().set(i, newNode);
            
        }
    }

    public boolean erase(int num) {
        Node<Integer> current = head;
        Node<Integer>[] update = new Node[MAX_LEVEL];
        for (int i = MAX_LEVEL - 1; i >= 0; i--) {
            while (current.getForword().size() > i && current.getForword().get(i) != null
                    && current.getForword().get(i).getValue() < num) {
                current = current.getForword().get(i);
            }
            update[i] = current;
        }

        if (current.getForword().size() > 0) {
            current = current.getForword().get(0);
        }
        if (current != null && current.getValue() == num) {
            for (int i = 0; i < MAX_LEVEL; i++) {
                if (update[i].getForword().size() > i && update[i].getForword().get(i) == current) {
                    update[i].getForword().set(i, current.getForword().size() > i ? current.getForword().get(i) : null);
                }
            }
            return true;
        }
        return false;
    }

    static class Node<T> {
        private T value;
        private final List<Node<T>> forword;

        public Node(T value, int level) {
            this.value = value;
            this.forword = new ArrayList<>();
            for (int i = 0; i < level; i++) {
                this.forword.add(null);
            }
        }

        public T getValue() {
            return value;
        }

        public List<Node<T>> getForword() {
            return forword;
        }
    }

    private int randomLevel() {
        int level = 1;
        while (level < MAX_LEVEL && random.nextDouble() < 0.5) {
            level++;
        }
        return level;
    }
}

链表

无重复字符的最长子串(3)

题目:

算法思路:

java代码:

链表

无重复字符的最长子串(3)

题目:

算法思路:

java代码: