5

呕心沥血!历时半年,谷歌学姐手写的算法刷题手册火了,完整版开放下载!

 3 years ago
source link: https://zhuanlan.zhihu.com/p/416936490
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

呕心沥血!历时半年,谷歌学姐手写的算法刷题手册火了,完整版开放下载!

FLAG资深工程师,美帝代码搬运工,公众号:九章算法

本文作者 | Andy
多伦多大学,计算机专业 | 刷题500+ | 现谷歌软件工程师(L5)

如果你刷题刷得非常吃力,那么一定要来看看我整理的这份【算法刷题笔记】

每一道题的模板、解法、思路都写得非常清楚。

这份笔记的初代版本来源于FB大佬令狐冲的cheetsheet,目前在Github上已经获得了1.5K☆。

v2-f035be6f1e84000718b9254a7aa2bc87_720w.jpg

我把他的刷题笔记进行了整理,于是这样一套LeetCode/LintCode最强刷题指南终于上线,这套模板支持Java/Python语言,列举了面试常考的十几个算法与数据结构的解题模板,面试中遇到就可以直接套。

这里我也把这套模板无私分享出来,更多模板及解析也可以移步《九章算法班》的免费试听讲座获取~

二分法 Binary Search

v2-b3f20f27fa3886522e9c9bbc36df25c4_720w.jpg

使用条件

  1. 排序数组 (30-40%是二分)
  2. 当面试官要求你找一个比 O(n) 更小的时间复杂度算法的时候(99%)
  3. 找到数组中的一个分割位置,使得左半部分满足某个条件,右半部分不满足(100%)
  4. 找到一个最大/最小的值使得某个条件被满足(90%)

复杂度

  1. 时间复杂度:O(logn)
  2. 空间复杂度:O(1)

领扣例题

LintCode 14. 二分查找(在排序的数据集上进行二分

二分查找的模版,找第一次出现的位置。

注意可能出现多次,所以当找到这个数字的时候不能直接结束,而要将end移动到mid处。

直到最后缩小到只有一个或两个数字时,优先判断start,再判断end。

时间复杂度O(log2n)

// version 1: with jiuzhang template 
class Solution { 
    /** 
     * @param nums: The integer array. 
     * @param target: Target to find. 
     * @return: The first position of target. Position starts from 0. 
     */ 
    public int binarySearch(int[] nums, int target) { 
        if (nums == null || nums.length == 0) { 
            return -1; 
        } 
 
        int start = 0, end = nums.length - 1; 
        while (start + 1 < end) { 
            int mid = start + (end - start) / 2; 
            if (nums[mid] == target) { 
                end = mid; 
            } else if (nums[mid] < target) { 
                start = mid; 
                // or start = mid + 1 
            } else { 
                end = mid; 
                // or end = mid - 1 
            } 
        } 
 
        if (nums[start] == target) { 
            return start; 
        } 
        if (nums[end] == target) { 
            return end; 
        } 
        return -1; 
    } 
} 
 
 
// version 2: without jiuzhang template 
class Solution { 
    /** 
     * @param nums: The integer array. 
     * @param target: Target to find. 
     * @return: The first position of target. Position starts from 0. 
     */ 
    public int binarySearch(int[] nums, int target) { 
        if (nums == null || nums.length == 0) { 
            return -1; 
        } 
 
        int start = 0, end = nums.length - 1; 
        while (start < end) { 
            int mid = start + (end - start) / 2; 
            if (nums[mid] == target) { 
                end = mid; 
            } else if (nums[mid] < target) { 
                start = mid + 1; 
            } else { 
                end = mid - 1; 
            } 
        } 
 
        if (nums[start] == target) { 
            return start; 
        } 
 
        return -1; 
    } 
} 

LintCode 460. 在排序数组中找最接近的K个数 (在未排序的数据集上进行二分)

采用的是二分法 + 双指针

二分法确定一个位置,左侧是 < target,右侧是 >= target

然后用两根指针从中间向两边走,依次找到最接近的 k 个数

与 Java 的版本的小区别是,起始位置是先找到右指针。然后左指针=右指针-1。

class Solution: 
    """ 
    @param A: an integer array 
    @param target: An integer 
    @param k: An integer 
    @return: an integer array 
    """ 
    def kClosestNumbers(self, A, target, k): 
        # 找到 A[left] < target, A[right] >= target 
        # 也就是最接近 target 的两个数,他们肯定是相邻的 
        right = self.findUpperClosest(A, target) 
        left = right - 1 
 
            # 两根指针从中间往两边扩展,依次找到最接近的 k 个数 
        results = [] 
        for _ in range(k): 
            if self.isLeftCloser(A, target, left, right): 
                results.append(A[left]) 
                left -= 1 
            else: 
                results.append(A[right]) 
                right += 1 
 
        return results 
 
    def findUpperClosest(self, A, target): 
        # find the first number >= target in A 
        start, end = 0, len(A) - 1 
        while start + 1 < end: 
            mid = (start + end) // 2 
            if A[mid] >= target: 
                end = mid 
            else: 
                start = mid 
 
        if A[start] >= target: 
            return start 
 
        if A[end] >= target:             
            return end 
 
        # 找不到的情况 
        return len(A) 
 
    def isLeftCloser(self, A, target, left, right): 
        if left < 0: 
            return False 
        if right >= len(A): 
            return True 
        return target - A[left] <= A[right] - target 
 

LintCode 437. 书籍复印(在答案集上进行二分 )

基于答案值域的二分法。 答案的范围在 max(pages)~sum(pages) 之间,每次二分到一个时间 time_limit 的时候,用贪心法从左到右扫描一下 pages,看看需要多少个人来完成抄袭。 如果这个值 <= k,那么意味着大家花的时间可能可以再少一些,如果 > k 则意味着人数不够,需要降低工作量。

时间复杂度 是该问题时间复杂度上的最优解法

class Solution: 
    """ 
    @param pages: an array of integers 
    @param k: An integer 
    @return: an integer 
    """ 
    def copyBooks(self, pages, k): 
        if not pages: 
            return 0 
 
        start, end = max(pages), sum(pages) 
        while start + 1 < end: 
            mid = (start + end) // 2 
            if self.get_least_people(pages, mid) <= k: 
                end = mid 
            else: 
                start = mid 
 
        if self.get_least_people(pages, start) <= k: 
            return start 
 
        return end 
 
    def get_least_people(self, pages, time_limit): 
        count = 0 
        time_cost = 0  
        for page in pages: 
            if time_cost + page > time_limit: 
                count += 1 
                time_cost = 0 
            time_cost += page 
 
        return count + 1 
 

代码模版

Java

Python

双指针 Two Pointers

使用条件

  1. 滑动窗口 (90%)
  2. 时间复杂度要求 O(n) (80%是双指针)
  3. 要求原地操作,只可以使用交换,不能使用额外空间 (80%)
  4. 有子数组 subarray /子字符串 substring 的关键词 (50%)
  5. 有回文 Palindrome 关键词(50%)

复杂度

  • 时间复杂度:O(n)
    • 时间复杂度与最内层循环主体的执行次数有关
  • 与有多少重循环无关
  • 空间复杂度:O(1)
    • 只需要分配两个指针的额外内存

领扣例题

LintCode 1879. 两数之和VII(同向双指针)

two pointer left 指向最小值,right 指向最大值。两指针分别移动,求和判断即可。 
public class Solution { 
    /** 
     * @param nums: the input array 
     * @param target: the target number 
     * @return: return the target pair 
     */ 
 
    public int nextleft(int left, int[] nums){ 
        int n = nums.length; 
        if (nums[left] < 0){ 
            for (int i = left - 1; i >= 0; i--) 
                if (nums[i] < 0) return i; 
            for (int i = 0; i < n; i++) 
                if (nums[i] >= 0) return i; 
            return -1; 
        } 
        for (int i = left + 1; i < n; i++) 
            if (nums[i] >= 0) return i; 
        return -1; 
    } 
 
    public int nextright(int right, int[] nums){ 
        int n = nums.length; 
        if (nums[right] > 0){ 
            for (int i = right - 1; i >= 0; i--) 
                if (nums[i] > 0) return i; 
            for (int i = 0; i < n; i++) 
                if (nums[i] <= 0) return i; 
            return -1; 
        } 
        for (int i = right + 1; i < n; i++) 
            if (nums[i] <= 0) return i; 
        return -1; 
    } 
 
    public List<List<Integer>> twoSumVII(int[] nums, int target) { 
        // write your code here 
        int n = nums.length; 
        List<List<Integer>> ans = new ArrayList<List<Integer>>(); 
        if (n == 0) return ans; 
        int left = 0, right = 0; 
        for (int i = 1; i < n; i++){ 
            if (nums[i] > nums[right]) right = i; 
            if (nums[i] < nums[left]) left = i; 
        } 
        while (nums[left] < nums[right]){ 
            if (nums[left] + nums[right] < target){ 
                left = nextleft(left, nums); 
                if (left == -1) break; 
            } 
            else if (nums[left] + nums[right] > target){ 
                right = nextright(right, nums); 
                if (right == -1) break; 
            } 
            else{ 
                List<Integer> tmp = new ArrayList<Integer>(); 
                if (left < right){ 
                    tmp.add(left); tmp.add(right); 
                } 
                else{ 
                    tmp.add(right); tmp.add(left); 
                } 
                ans.add(tmp); 
                left = nextleft(left, nums); 
                if (left == -1) break; 
            } 
        } 
        return ans; 
    } 
} 

LintCodehttps://www.lintcode.com/problem/1712/?utm_source=sc-zhihuzl-sy08191712.和相同的二元子数组(相向双指针)

前缀和:定义一个数组sumN+1,si表示数组A中前i个元素之和,然后遍历sum数组,计算si+S(含义:前i个元素之和是si,找和为S的子数组个数)。求si+S的个数

class Solution { 
public: 
    /** 
     * @param A: an array 
     * @param S: the sum 
     * @return: the number of non-empty subarrays 
     */ 
    int numSubarraysWithSum(vector<int> &A, int S) { 
        // Write your code here. 
        unordered_map<int, int> c({{0, 1}}); 
        int psum = 0, res = 0; 
        for (int i : A) { 
            psum += i; 
            res += c[psum - S]; 
            c[psum]++; 
        } 
        return res; 
    } 
}; 
 

LintCodehttps://www.lintcode.com/problem/627/?utm_source=sc-zhihuzl-sy1004-2627. 最长回文串 (背向双指针)

算法:贪心

回文串是一个正读和反读都一样的字符串,因此可以发现一个特点是所有字符最多只允许一种字符出现奇数次,其余均为偶数次。那么我们可以这样构造一个回文串,将出现奇数次的字符(如果有的话)置于中心位置,然后将剩余的偶数次字符不断放置在两侧。

算法思路

  • 题目需要我们构造出最长的回文串,因此我们可以想到用贪心的算法来解决这个问题

步骤

  1. 统计每种字符出现的次数,注意本题需要大小写区分;
  2. 对于一个字符c,如果出现了cnt次,那么我们可以将这个字符在字符串两侧各放置 cnt/2个,即一共使用了 cnt/2*2个(/为整除法),将它计入答案ans
  3. 如果有字符出现奇数次,那么我们将其放在回文串中心,ans=ans+1

复杂度分析

  • 空间复杂度:O(N),N为字符串长度。
  • 时间复杂度:O(N),需要遍历到字符串中每个字符并进行一次计数。
public class Solution { 
    /** 
     * @param s a string which consists of lowercase or uppercase letters 
     * @return the length of the longest palindromes that can be built 
     */ 
    public int longestPalindrome(String s) { 
        //cnt为计数数组 
        //OddCount为是否有奇数次字符,1表示有,0表示无 
        //ans为最终答案 
        int[] cnt = new int[52]; 
        int OddCount = 0; 
        int ans = 0; 
 
        // 统计字符串s中每种字母出现次数 
        for (char c: s.toCharArray()) { 
            // 是小写字母 
            if (c >= 97) { 
                cnt[26 + c - 'a']++; 
            } 
            // 是大写字母 
            else { 
                cnt[c - 'A']++; 
            } 
        } 
 
        // 每种字符可使用cnt/2*2次 
        // 若有字符出现奇数次,将OddCount值改为1 
        for (int i = 0; i < 52; i++) { 
            ans += cnt[i]/2*2; 
            if (cnt[i] % 2 == 1) { 
                OddCount = 1; 
            } 
        } 
        //若有字符出现奇数次,答案加1 
        ans += OddCount; 
        return ans; 
    } 
} 
 

LintCode 64: 合并有序数组

使用两个指针分别对数组从小到大遍历,每次取二者中较小的放在新数组中。 直到某个指针先到结尾,另一个数组中剩余的数字直接放在新数组后面。

时间复杂度O(n)

class Solution: 
    """ 
    @param: A: sorted integer array A which has m elements, but size of A is m+n 
    @param: m: An integer 
    @param: B: sorted integer array B which has n elements 
    @param: n: An integer 
    @return: nothing 
    """ 
    def mergeSortedArray(self, A, m, B, n): 
        if A is None or B is None: 
            return  
 
        i, j = 0, 0 
        array = [] 
 
        while i < m and j < n: 
            if A[i] <= B[j]: 
                array.append(A[i]) 
                i += 1 
            else: 
                array.append(B[j]) 
                j += 1 
 
        while i < m: 
            array.append(A[i]) 
            i += 1 
        while j < n: 
            array.append(B[j]) 
            j += 1 
 
        for index in range(m + n): 
            A[index] = array[index] 
 
        return A 
 

代码模版

Java

Python

排序算法 Sorting

使用条件

  • 时间复杂度:
    • 快速排序(期望复杂度) : O(nlogn)
  • 归并排序(最坏复杂度) : O(nlogn)
  • 空间复杂度:
    • 快速排序 : O(1)
    • 归并排序 : O(n)

领扣例题

LintCode 463. 整数排序

补充了各个算法的实现

class Solution: 
    # @param {int[]} A an integer array 
    # @return nothing 
    def sortIntegers(self, A): 
        # Write your code here 
        A.sort() 
 
# 选择排序 
class Solution: 
    # @param {int[]} A an integer array 
    # @return nothing 
    def sortIntegers(self, A): 
        # Write your code here 
        for i in range(0, len(A)): 
            t = i 
            for j in range(i + 1, len(A)): 
                if A[j] < A[t]: 
                    t = j 
            A[i], A[t] = A[t], A[i] 
 
# 插入排序 
class Solution: 
    # @param {int[]} A an integer array 
    # @return nothing 
    def sortIntegers(self, A): 
        # Write your code here 
        for i in range(1, len(A) + 1): 
            for j in range(i - 1, 0, -1): 
                if A[j - 1] > A[j]: 
                    A[j - 1], A[j] = A[j], A[j - 1] 
                else : 
                    break 
 
# 冒泡排序 
class Solution: 
    # @param {int[]} A an integer array 
    # @return nothing 
    def sortIntegers(self, A): 
        # Write your code here 
        for i in range(0, len(A) - 1): 
            for j in range(0, len(A) - 1): 
                if A[j] > A[j + 1]: 
                    A[j], A[j + 1] = A[j + 1], A[j] 

LintCode 464. 整数排序 II

快速排序实现

public class Solution { 
    /** 
     * @param A an integer array 
     * @return void 
     */ 
    public void sortIntegers2(int[] A) { 
        quickSort(A, 0, A.length - 1); 
    } 
 
    private void quickSort(int[] A, int start, int end) { 
        if (start >= end) { 
            return; 
        } 
 
        int left = start, right = end; 
        // key point 1: pivot is the value, not the index 
        int pivot = A[(start + end) / 2]; 
 
        // key point 2: every time you compare left & right, it should be  
        // left <= right not left < right 
        while (left <= right) { 
            while (left <= right && A[left] < pivot) { 
                left++; 
            } 
            while (left <= right && A[right] > pivot) { 
                right--; 
            } 
            if (left <= right) { 
                int temp = A[left]; 
                A[left] = A[right]; 
                A[right] = temp; 
 
                left++; 
                right--; 
            } 
        } 
 
        quickSort(A, start, right); 
        quickSort(A, left, end); 
    } 
} 

代码模板

Java



Python

二叉树分治 Binary Tree Divide & Conquer

使用条件

  • 二叉树相关的问题 (99%)
  • 可以一分为二去分别处理之后再合并结果 (100%)
  • 数组相关的问题 (10%)

复杂度

时间复杂度 O(n)

空间复杂度 O(n) (含递归调用的栈空间最大耗费)

领扣例题

LintCode 1534. 将二叉搜索树转换为已排序的双向链接列表

根据二叉搜索树的中序遍历得到双向链表的节点值,最后构建一个新的双向链表即可。

public class Solution { 
    /** 
     * @param root: root of a tree 
     * @return: head node of a doubly linked list 
     */ 
    public TreeNode treeToDoublyList(TreeNode root) { 
        // Write your code here. 
        if (root == null) { 
            return null; 
        } 
        List<TreeNode> list = new ArrayList<>(); 
        helper(root, list); 
 
        TreeNode head = list.get(0); 
        TreeNode tail = list.get(list.size() - 1); 
        head.left = tail; 
        tail.right = head; 
        return head; 
    } 
 
    private void helper(TreeNode node, List<TreeNode> list) { 
        if (node == null) { 
            return; 
        } 
        helper(node.left, list); 
        list.add(node); 
        if (list.size() >= 2) { 
            int n = list.size(); 
            TreeNode a = list.get(n - 2); 
            TreeNode b = list.get(n - 1); 
            a.right = b; 
            b.left = a; 
        } 
        helper(node.right, list); 
    } 
} 

LintCode 94. 二叉树中的最大路径和

在遍历树的过程中,要看两样,一是当前subtree子树的最大值,一是当前subtree能对parent tree父树贡献什么。

第一个并不需在递归函数里返回,多个Return Type 在这里并不好懂,有削足适履的感觉。用全局变量更合适。

这题,局部最大值是遍历算法,挑选左右子树最大值是分治法。这里的解法是合二为一,虽简单,但很有启发。值得多打几次。

class Solution: 
 
    def maxPathSum(self, root): 
        self.maxSum = -sys.maxsize-1 
 
        self.helper(root) 
 
        return self.maxSum  
 
    def helper(self, root): 
 
        if not root: return 0  
 
        leftSum = self.helper(root.left) 
        if leftSum < 0: 
            leftSum = 0 
        rightSum = self.helper(root.right) 
        if rightSum < 0: 
            rightSum = 0 
 
        self.maxSum = max(self.maxSum, root.val + leftSum + rightSum) 
 
        return max(root.val + leftSum, root.val + rightSum) 

LintCode 95.验证二叉查找树

分治法,但是 minValue 和 maxValue 用 minNode 和 maxNode 来代替。 
/** 
 * Definition of TreeNode: 
 * public class TreeNode { 
 *     public int val; 
 *     public TreeNode left, right; 
 *     public TreeNode(int val) { 
 *         this.val = val; 
 *         this.left = this.right = null; 
 *     } 
 * } 
 */ 
 
class ResultType { 
    public boolean isBST; 
    public TreeNode maxNode, minNode; 
    public ResultType(boolean isBST) { 
        this.isBST = isBST; 
        this.maxNode = null; 
        this.minNode = null; 
    } 
} 
 
public class Solution { 
    /** 
     * @param root: The root of binary tree. 
     * @return: True if the binary tree is BST, or false 
     */ 
    public boolean isValidBST(TreeNode root) { 
        return divideConquer(root).isBST; 
    } 
 
    private ResultType divideConquer(TreeNode root) { 
        if (root == null) { 
            return new ResultType(true); 
        } 
 
        ResultType left = divideConquer(root.left); 
        ResultType right = divideConquer(root.right); 
        if (!left.isBST || !right.isBST) { 
            return new ResultType(false); 
        } 
 
        if (left.maxNode != null && left.maxNode.val >= root.val) { 
            return new ResultType(false); 
        } 
 
        if (right.minNode != null && right.minNode.val <= root.val) { 
            return new ResultType(false); 
        } 
 
        // is bst 
        ResultType result = new ResultType(true); 
        result.minNode = left.minNode != null ? left.minNode : root; 
        result.maxNode = right.maxNode != null ? right.maxNode : root; 
 
        return result; 
    } 
} 

代码模板

Java

Python

二叉搜索树非递归 BST Iterator

使用条件

  • 用非递归的方式(Non-recursion / Iteration)实现二叉树的中序遍历
  • 常用于 BST 但不仅仅可以用于 BST

复杂度

时间复杂度 O(n)

空间复杂度 O(n)

领扣例题

LintCode 67. 二叉树的中序遍历

首先访问左子树,将左子树存入栈中,每次将栈顶元素存入结果,如果右子树为空,取出栈顶元素,如果当前元素为栈顶元素右子树,一直弹出至当前元素不为栈顶元素右子树(此处说明访问右子树,根节点已经被访问过,弹出即可)。如果节点右子树不为空,访问右子树,继续循环遍历左子树,存入栈中。

public class Solution { 
    /** 
     * @param root: The root of binary tree. 
     * @return: Inorder in ArrayList which contains node values. 
     */ 
    public ArrayList<Integer> inorderTraversal(TreeNode root) { 
        Stack<TreeNode> stack = new Stack<>(); 
        ArrayList<Integer> result = new ArrayList<>(); 
 
        while (root != null) { 
            stack.push(root); 
            root = root.left; 
        } 
 
        while (!stack.isEmpty()) { 
            TreeNode node = stack.peek(); 
            result.add(node.val); 
 
            if (node.right == null) { 
                node = stack.pop(); 
                while (!stack.isEmpty() && stack.peek().right == node) { 
                    node = stack.pop(); 
                } 
            } else { 
                node = node.right; 
                while (node != null) { 
                    stack.push(node); 
                    node = node.left; 
                } 
            } 
        } 
        return result; 
    } 
} 

LintCode 902. 二叉搜索树的第 k 大元素

时间复杂度 O(n) 最好最坏都是。

算法思想类似于 Quick Select。

这个算法的好处是,如果多次查询的话,给每个节点统计儿子个数这个过程只需要做一次。查询可以很快。

public class Solution { 
    /** 
     * @param root: the given BST 
     * @param k: the given k 
     * @return: the kth smallest element in BST 
     */ 
    public int kthSmallest(TreeNode root, int k) { 
        Map<TreeNode, Integer> numOfChildren = new HashMap<>(); 
        countNodes(root, numOfChildren); 
        return quickSelectOnTree(root, k, numOfChildren); 
    } 
 
    private int countNodes(TreeNode root, Map<TreeNode, Integer> numOfChildren) { 
        if (root == null) { 
            return 0; 
        } 
 
        int left = countNodes(root.left, numOfChildren); 
        int right = countNodes(root.right, numOfChildren); 
        numOfChildren.put(root, left + right + 1); 
        return left + right + 1; 
    } 
 
    private int quickSelectOnTree(TreeNode root, int k, Map<TreeNode, Integer> numOfChildren) { 
        if (root == null) { 
            return -1; 
        } 
 
        int left = root.left == null ? 0 : numOfChildren.get(root.left); 
        if (left >= k) { 
            return quickSelectOnTree(root.left, k, numOfChildren); 
        } 
        if (left + 1 == k) { 
            return root.val; 
        } 
 
        return quickSelectOnTree(root.right, k - left - 1, numOfChildren); 
    } 
} 

代码模板

Java

Python

宽度优先搜索 BFS

使用条件

  1. 拓扑排序(100%)
  2. 出现连通块的关键词(100%)
  3. 分层遍历(100%)
  4. 简单图最短路径(100%)
  5. 给定一个变换规则,从初始状态变到终止状态最少几步(100%)

复杂度

  • 时间复杂度:O(n + m)
    • n 是点数, m 是边数
  • 空间复杂度:O(n)

领扣例题

LintCode 974. 01 矩阵(分层遍历)

  • 使用BFS来做,需要pair queue分别记录row和col
  • initialize distance 的时候 - 把0的distance标记成0
    • 把1的distance标记成INT_MAX,以便区分
  • 必须要从0去找1,也就是说,把0放到queue里而不是把1放到queue - 因为0 是有特征的(特征为distance = 0),但是1没有特征,所有的1都是一样的
    • 所以如果从1去找0 (把1放到queue里面)就会找不到
  • 注意搜索的时候要有4个方向
class Solution { 
public: 
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) { 
        if (matrix.empty() || matrix[0].empty()) { 
            return matrix; 
        } 
 
        int rowSize = matrix.size(); 
        int colSize = matrix[0].size();         
 
        vector<vector<int>> distance(rowSize, vector<int> (colSize, INT_MAX)); 
        queue<pair<int, int>> q; 
 
        for(int i = 0; i < rowSize; i++) { 
            for (int j = 0; j < colSize; j++) { 
                if (matrix[i][j] == 0) { 
                    q.push({i, j}); 
                    distance[i][j] = 0; 
                } 
            } 
        } 
 
        int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; 
 
        while (!q.empty()) { 
            pair<int, int> cur = q.front(); 
            q.pop(); 
 
            for (int i = 0; i < 4; i++) { 
                int newRow = cur.first + dir[i][0]; 
                int newCol = cur.second + dir[i][1]; 
 
                // check for boundary 
                if (newRow >= 0 && newRow < rowSize && newCol >= 0 && newCol < colSize) { 
                    if (distance[newRow][newCol] > distance[cur.first][cur.second] + 1) { 
                        distance[newRow][newCol] = distance[cur.first][cur.second] + 1; 
                        q.push({newRow, newCol}); 
                    } 
                }  
            }             
        } 
 
        return distance; 
    } 
}; 

LintCode 431. 找无向图的连通块

无向图连通块, 可以使用BFS或者并查集union find求解 .

// bfs 方法 
/** 
 * Definition for Undirected graph. 
 * class UndirectedGraphNode { 
 *     int label; 
 *     ArrayList<UndirectedGraphNode> neighbors; 
 *     UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); } 
 * }; 
 */ 
public class Solution { 
    /** 
     * @param nodes a array of Undirected graph node 
     * @return a connected set of a Undirected graph 
     */ 
    public List<List<Integer>> connectedSet(List<UndirectedGraphNode> nodes) { 
        // Write your code here 
 
        Map<UndirectedGraphNode, Boolean> visited = new HashMap<>(); 
 
       for (UndirectedGraphNode node : nodes){ 
            visited.put(node, false); 
       } 
 
        List<List<Integer>> result = new ArrayList<>(); 
 
        for (UndirectedGraphNode node : nodes){ 
            if (visited.get(node) == false){ 
                bfs(node, visited, result); 
            } 
        } 
 
        return result; 
    } 
 
    public void bfs(UndirectedGraphNode node, Map<UndirectedGraphNode, Boolean> visited, List<List<Integer>> result){ 
        List<Integer>row = new ArrayList<>(); 
        Queue<UndirectedGraphNode> queue = new LinkedList<>(); 
        visited.put(node, true); 
        queue.offer(node); 
        while (!queue.isEmpty()){ 
            UndirectedGraphNode u = queue.poll(); 
            row.add(u.label);     
            for (UndirectedGraphNode v : u.neighbors){ 
                if (visited.get(v) == false){ 
                    visited.put(v, true); 
                    queue.offer(v); 
                } 
            } 
        } 
        Collections.sort(row); 
        result.add(row); 
 
    } 
} 
 
//------------------------我是分割线-------------------------------------- 
// 并查集方法 
public 
class Solution { 
    class UnionFind { 
        HashMap<Integer, Integer> father = new HashMap<Integer, Integer>(); 
        UnionFind(HashSet<Integer> hashSet) 
        { 
            for (Integer now : hashSet) { 
                father.put(now, now); 
            } 
        } 
        int find(int x) 
        { 
            int parent = father.get(x); 
            while (parent != father.get(parent)) { 
                parent = father.get(parent); 
            } 
            return parent; 
        } 
        int compressed_find(int x) 
        { 
            int parent = father.get(x); 
            while (parent != father.get(parent)) { 
                parent = father.get(parent); 
            } 
            int next; 
            while (x != father.get(x)) { 
                next = father.get(x); 
                father.put(x, parent); 
                x = next; 
            } 
            return parent; 
        } 
 
        void union(int x, int y) 
        { 
            int fa_x = find(x); 
            int fa_y = find(y); 
            if (fa_x != fa_y) 
                father.put(fa_x, fa_y); 
        } 
    } 
 
    List<List<Integer> > print(HashSet<Integer> hashSet, UnionFind uf, int n) { 
        List<List<Integer> > ans = new ArrayList<List<Integer> >(); 
        HashMap<Integer, List<Integer> > hashMap = new HashMap<Integer, List<Integer> >(); 
        for (int i : hashSet) { 
            int fa = uf.find(i); 
            if (!hashMap.containsKey(fa)) { 
                hashMap.put(fa, new ArrayList<Integer>()); 
            } 
            List<Integer> now = hashMap.get(fa); 
            now.add(i); 
            hashMap.put(fa, now); 
        } 
        for (List<Integer> now : hashMap.values()) { 
            Collections.sort(now); 
            ans.add(now); 
        } 
        return ans; 
    } 
 
public 
    List<List<Integer> > connectedSet(List<UndirectedGraphNode> nodes) 
    { 
        // Write your code here 
 
        HashSet<Integer> hashSet = new HashSet<Integer>(); 
        for (UndirectedGraphNode now : nodes) { 
            hashSet.add(now.label); 
            for (UndirectedGraphNode neighbour : now.neighbors) { 
                hashSet.add(neighbour.label); 
            } 
        } 
        UnionFind uf = new UnionFind(hashSet); 
 
        for (UndirectedGraphNode now : nodes) { 
 
            for (UndirectedGraphNode neighbour : now.neighbors) { 
                int fnow = uf.find(now.label); 
                int fneighbour = uf.find(neighbour.label); 
                if (fnow != fneighbour) { 
                    uf.union(now.label, neighbour.label); 
                } 
            } 
        } 
 
        return print(hashSet, uf, nodes.size()); 
    } 
} 
 

LintCode 127. 拓扑排序

class Solution: 
    """ 
    @param graph: A list of Directed graph node 
    @return: Any topological order for the given graph. 
    """ 
    def topSort(self, graph): 
        node_to_indegree = self.get_indegree(graph) 
 
        # bfs 
        order = [] 
        start_nodes = [n for n in graph if node_to_indegree[n] == 0] 
        queue = collections.deque(start_nodes) 
        while queue: 
            node = queue.popleft() 
            order.append(node) 
            for neighbor in node.neighbors: 
                node_to_indegree[neighbor] -= 1 
                if node_to_indegree[neighbor] == 0: 
                    queue.append(neighbor) 
 
        return order 
 
    def get_indegree(self, graph): 
        node_to_indegree = {x: 0 for x in graph} 
 
        for node in graph: 
            for neighbor in node.neighbors: 
                node_to_indegree[neighbor] += 1 
 
        return node_to_indegree 

代码模版

Java

Python

Java 拓扑排序 BFS 模板

Python

深度优先搜索 DFS

使用条件

  • 找满足某个条件的所有方案 (99%)
  • 二叉树 Binary Tree 的问题 (90%)
  • 组合问题(95%)
    • 问题模型:求出所有满足条件的“组合”
    • 判断条件:组合中的元素是顺序无关的
  • 排列问题 (95%)
    • 问题模型:求出所有满足条件的“排列”
    • 判断条件:组合中的元素是顺序“相关”的。

不要用 DFS 的场景

  1. 连通块问题(一定要用 BFS,否则 StackOverflow)
  2. 拓扑排序(一定要用 BFS,否则 StackOverflow)
  3. 一切 BFS 可以解决的问题

复杂度

  • 时间复杂度:O(方案个数 * 构造每个方案的时间)
    • 树的遍历 : O(n)
  • 排列问题 : O(n! * n)
  • 组合问题 : O(2^n * n)

领扣例题

LintCode 67.二叉树的中序遍历(遍历树)

首先访问左子树,将左子树存入栈中,每次将栈顶元素存入结果,如果右子树为空,取出栈顶元素,如果当前元素为栈顶元素右子树,一直弹出至当前元素不为栈顶元素右子树(此处说明访问右子树,根节点已经被访问过,弹出即可)。如果节点右子树不为空,访问右子树,继续循环遍历左子树,存入栈中。

public class Solution { 
    /** 
     * @param root: The root of binary tree. 
     * @return: Inorder in ArrayList which contains node values. 
     */ 
    public ArrayList<Integer> inorderTraversal(TreeNode root) { 
        Stack<TreeNode> stack = new Stack<>(); 
        ArrayList<Integer> result = new ArrayList<>(); 
 
        while (root != null) { 
            stack.push(root); 
            root = root.left; 
        } 
 
        while (!stack.isEmpty()) { 
            TreeNode node = stack.peek(); 
            result.add(node.val); 
 
            if (node.right == null) { 
                node = stack.pop(); 
                while (!stack.isEmpty() && stack.peek().right == node) { 
                    node = stack.pop(); 
                } 
            } else { 
                node = node.right; 
                while (node != null) { 
                    stack.push(node); 
                    node = node.left; 
                } 
            } 
        } 
        return result; 
    } 
} 

LintCode 652.因式分解(枚举所有情况)

使用dfs解决问题。

使用List来保存当前已用因数,进入dfs时在最后一位添加一个因数,从dfs中返回时删除最后一个元素。

当dfs至num=1时,将当前的list存入答案。

public class Solution { 
    /** 
     * @param n an integer 
     * @return a list of combination 
     */ 
    public List<List<Integer>> getFactors(int n) { 
        // write your code here 
        List<List<Integer>> result = new ArrayList<List<Integer>>(); 
        helper(result, new ArrayList<Integer>(), n, 2); 
        return result; 
    } 
 
    public void helper(List<List<Integer>> result, List<Integer> item, int n, int start) { 
        if (n <= 1) { 
            if (item.size() > 1) { 
                result.add(new ArrayList<Integer>(item)); 
            } 
            return; 
        } 
 
        for (int i = start; i <= Math.sqrt(n); ++i) { 
            if (n % i == 0) { 
                item.add(i); 
                helper(result, item, n / i, i); 
                item.remove(item.size()-1); 
            } 
        }  
        if (n >= start) { 
            item.add(n); 
            helper(result, item, 1, n); 
            item.remove(item.size() - 1); 
        } 
    } 
} 
 
// version: 高频题班 
public class Solution { 
    /** 
     * @param n an integer 
     * @return a list of combination 
     */ 
 
    List<List<Integer>> ans = new ArrayList<>(); 
    List<Integer> path = new ArrayList<>(); 
 
    void dfs(int start, int remain) { 
        if (remain == 1) { 
            if (path.size() != 1) { 
                ans.add(new ArrayList<>(path)); //deep copy 
            } 
            return; 
        } 
 
        for (int i = start; i <= remain; i++) { 
            if (i > remain / i) { 
                break; 
            } 
            if (remain % i == 0) { 
                path.add(i);                  //进栈 
                dfs(i, remain / i); 
                path.remove(path.size() - 1); //出栈 
            } 
        } 
 
        path.add(remain); 
        dfs(remain, 1); 
        path.remove(path.size() - 1); 
    } 
 
    public List<List<Integer>> getFactors(int n) { 
        // write your code here 
        dfs(2, n); 
        return ans; 
    } 
} 
 

代码模版

Java

Python

动态规划 Dynamic Programming

使用条件

  • 使用场景:
    • 求方案总数(90%)
    • 求最值(80%)
    • 求可行性(80%)
  • 不适用的场景:
    • 找所有具体的方案(准确率99%)
    • 输入数据无序(除了背包问题外,准确率60%~70%)
    • 暴力算法已经是多项式时间复杂度(准确率80%)
  • 动态规划四要素(对比递归的四要素):
    • 状态 (State) -- 递归的定义
    • 方程 (Function) -- 递归的拆解
    • 初始化 (Initialization) -- 递归的出口
    • 答案 (Answer) -- 递归的调用
  • 几种常见的动态规划:
  • 背包型
    • 给出 n 个物品及其大小,问是否能挑选出一些物品装满大小为m的背包
    • 题目中通常有“和”与“差”的概念,数值会被放到状态中
    • 通常是二维的状态数组,前 i 个组成和为 j 状态数组的大小需要开 (n + 1) * (m + 1)
    • 几种背包类型:
        • 状态 state

dp[i][j] 表示前 i 个数里挑若干个数是否能组成和为 j

方程 function

dp[i][j] = dp[i - 1][j] or dp[i - 1][j - A[i - 1]] 如果 j >= A[i - 1]

dp[i][j] = dp[i - 1][j] 如果 j < A[i - 1]

第 i 个数的下标是 i - 1,所以用的是 A[i - 1] 而不是 A[i]

初始化 initialization

dp[0][0] = true

dp[0][1...m] = false

答案 answer

使得 dp[n][v], 0 s <= v <= m 为 true 的最大 v

    • 状态 state

dp[i][j] 表示前i个物品挑出一些放到 j 的背包里的最大价值和

方程 function

dp[i][j] = max(dp[i - 1][j - count * A[i - 1]] + count * V[i - 1])

其中 0 <= count <= j / A[i - 1]

初始化 initialization

dp[0][0..m] = 0

答案 answer

dp[n][m]

  • 区间型
  • 题目中有 subarray / substring 的信息
    • 大区间依赖小区间
    • dp[i][j] 表示数组/字符串中 i, j 这一段区间的最优值/可行性/方案总数
    • 状态 state

dp[i][j] 表示数组/字符串中 i,j 这一段区间的最优值/可行性/方案总数

方程 function

dp[i][j] = max/min/sum/or(dp[i,j 之内更小的若干区间])

  • 匹配型
    • 通常给出两个字符串
    • 两个字符串的匹配值依赖于两个字符串前缀的匹配值
    • 字符串长度为 n,m 则需要开 (n + 1) x (m + 1) 的状态数组
    • 要初始化 dp[i][0]dp[0][i]
    • 通常都可以用滚动数组进行空间优化
    • 状态 state

dp[i][j] 表示第一个字符串的前 i 个字符与第二个字符串的前 j 个字符怎么样怎么样(max/min/sum/or)

  • 划分型
    • 是前缀型动态规划的一种, 有前缀的思想
    • 如果指定了要划分为几个部分:
      • dp[i][j] 表示前i个数/字符划分为j个 部分的最优值/方案数/可行性
    • 如果没有指定划分为几个部分:
      • dp[i] 表示前i个数/字符划分为若干个 部分的最优值/方案数/可行性
    • 状态 state

指定了要划分为几个部分:dp[i][j] 表示前i个数/字符划分为j个部分的最优值/方案数/可行性

没有指定划分为几个部分: dp[i] 表示前i个数/字符划分为若干个部分的最优值/方案数/可行性

  • 接龙型
    • 通常会给一个接龙规则,问你最长的龙有多长
    • 状态表示通常为: dp[i] 表示以坐标为 i 的元素结尾的最长龙的长度
    • 方程通常是: dp[i] = max{dp[j] + 1}, j 的后面可以接上 i
    • LIS 的二分做法选择性的掌握,但并不是所有的接龙型DP都可以用二分来优化
    • 状态 state

状态表示通常为: dp[i] 表示以坐标为 i 的元素结尾的最长龙的长度

方程 function

dp[i] = max{dp[j] + 1}, j 的后面可以接上 i

  • 时间复杂度:
    • O(状态总数 * 每个状态的处理耗费)
    • 等于O(状态总数 * 决策数)
  • 空间复杂度:
    • O(状态总数) (不使用滚动数组优化)
    • O(状态总数 / n)(使用滚动数组优化, n是被滚动掉的那一个维度)

领扣例题

LintCode563.背包问题V(背包型):

带一点优化的动态规划。

  • 滚动数组。优化了空间。
  • 前缀和。优化了时间。没有必要每次都算到 target 那么多的值。
class Solution: 
    """ 
    @param nums: an integer array and all positive numbers 
    @param target: An integer 
    @return: An integer 
    """ 
    def backPackV(self, nums, target): 
        f = [[0] * (target + 1), [0] * (target + 1)] 
        f[0][0] = 1 
        n = len(nums) 
        prefix_sum = 0 
        for i in range(1, n + 1): 
            f[i % 2][0] = 1 
            prefix_sum += nums[i - 1] 
            for j in range(1, min(target, prefix_sum) + 1): 
                f[i % 2][j] = f[(i - 1) % 2][j] 
                if j >= nums[i - 1]: 
                    f[i % 2][j] += f[(i - 1) % 2][j - nums[i - 1]] 
 
        return f[n % 2][target] 
 

LintCode76.最长上升子序列(接龙型):

使用动态规划计算 Longest Increasing Subsequence,并同时打印具体的方案

class Solution: 
    """ 
    @param nums: The integer array 
    @return: The length of LIS (longest increasing subsequence) 
    """ 
    def longestIncreasingSubsequence(self, nums): 
        if nums is None or not nums: 
            return 0 
 
        # state: dp[i] 表示从左到右跳到i的最长sequence 的长度 
 
        # initialization: dp[0..n-1] = 1 
        dp = [1] * len(nums) 
 
        # prev[i] 代表 dp[i] 的最优值是从哪个 dp[j] 算过来的 
        prev = [-1] * len(nums) 
 
        # function dp[i] = max{dp[j] + 1},  j < i and nums[j] < nums[i] 
        for i in range(len(nums)): 
            for j in range(i): 
                if nums[j] < nums[i] and dp[i] < dp[j] + 1: 
                    dp[i] = dp[j] + 1 
                    prev[i] = j 
 
        # answer: max(dp[0..n-1]) 
        longest, last = 0, -1 
        for i in range(len(nums)): 
            if dp[i] > longest: 
                longest = dp[i] 
                last = i 
 
        path = [] 
        while last != -1: 
            path.append(nums[last]) 
            last = prev[last] 
        print(path[::-1]) 
 
        return longest 

LintCode 476.石子归并V(区间型):

使用四边形不等式优化之后的算法,时间复杂度 O(n

记录 cut[i][j] 代表使得 dp[i][j] 获得最优值的那个划分方案(切割位置)

可以证明(证明太长就不写了) cut[i][j - 1] <= cut[i][j] <= cut[i + 1][j]

class Solution: 
    """ 
    @param A: An integer array 
    @return: An integer 
    """ 
    def stoneGame(self, A): 
        n = len(A) 
        if n < 2: 
            return 0 
 
        # dp[i][j] => minimum cost merge from i to j 
        dp = [[0] * n for _ in range(n)] 
        # cut[i][j] => the best middle point that make dp[i][j] has the minimum cost 
        cut = [[0] * n for _ in range(n)] 
        # range_sum[i][j] => A[i] + A[i + 1] ... + A[j] 
        range_sum = self.get_range_sum(A) 
 
        for i in range(n - 1): 
            dp[i][i + 1] = A[i] + A[i + 1] 
            cut[i][i + 1] = i 
 
        # enumerate the range size first, start point second 
        for length in range(3, n + 1): 
            for i in range(n - length + 1): 
                j = i + length - 1 
                dp[i][j] = sys.maxsize 
                for mid in range(cut[i][j - 1], cut[i + 1][j] + 1): 
                    if dp[i][j] > dp[i][mid] + dp[mid + 1][j] + range_sum[i][j]: 
                        dp[i][j] = dp[i][mid] + dp[mid + 1][j] + range_sum[i][j] 
                        cut[i][j] = mid 
 
        return dp[0][n - 1] 
 
    def get_range_sum(self, A): 
        n = len(A) 
        range_sum = [[0] * n for _ in range(len(A))] 
        for i in range(n): 
            range_sum[i][i] = A[i] 
            for j in range(i + 1, n): 
                range_sum[i][j] = range_sum[i][j - 1] + A[j] 
        return range_sum 

LintCode 192. 通配符匹配 (匹配型):

使用深度优先搜索 + 记忆化的版本。

用一个二维的 boolean 数组来当记忆化数组,记录 s 从 sIndex 开始的后缀 能够匹配上 p 从 pIndex 开始的后缀

public class Solution { 
    /** 
     * @param s: A string  
     * @param p: A string includes "?" and "*" 
     * @return: is Match? 
     */ 
    public boolean isMatch(String s, String p) { 
        if (s == null || p == null) { 
            return false; 
        } 
 
        boolean[][] memo = new boolean[s.length()][p.length()]; 
        boolean[][] visited = new boolean[s.length()][p.length()]; 
        return isMatchHelper(s, 0, p, 0, memo, visited); 
    } 
 
    private boolean isMatchHelper(String s, int sIndex, 
                                  String p, int pIndex, 
                                  boolean[][] memo, 
                                  boolean[][] visited) { 
        // 如果 p 从pIdex开始是空字符串了,那么 s 也必须从 sIndex 是空才能匹配上 
        if (pIndex == p.length()) { 
            return sIndex == s.length(); 
        } 
 
        // 如果 s 从 sIndex 是空,那么p 必须全是 *  
        if (sIndex == s.length()) { 
            return allStar(p, pIndex); 
        } 
 
        if (visited[sIndex][pIndex]) { 
            return memo[sIndex][pIndex]; 
        } 
 
        char sChar = s.charAt(sIndex); 
        char pChar = p.charAt(pIndex); 
        boolean match; 
 
        if (pChar == '*') { 
            match = isMatchHelper(s, sIndex, p, pIndex + 1, memo, visited) || 
                isMatchHelper(s, sIndex + 1, p, pIndex, memo, visited); 
        } else { 
            match = charMatch(sChar, pChar) && 
                isMatchHelper(s, sIndex + 1, p, pIndex + 1, memo, visited); 
        } 
 
        visited[sIndex][pIndex] = true; 
        memo[sIndex][pIndex] = match; 
        return match; 
    } 
 
    private boolean charMatch(char sChar, char pChar) { 
        return (sChar == pChar || pChar == '?'); 
    } 
 
    private boolean allStar(String p, int pIndex) { 
        for (int i = pIndex; i < p.length(); i++) { 
            if (p.charAt(i) != '*') { 
                return false; 
            } 
        } 
        return true; 
    } 
} 

LintCode107.单词拆分(划分型):

使用《九章算法班》中讲过的划分型动态规划算法

  • state: dp[i] 表示前 i 个字符是否能够被划分为若干个单词
  • function: dp[i] = or{dp[j] and j + 1~i 是一个单词}
class Solution: 
    """ 
    @param: s: A string 
    @param: dict: A dictionary of words dict 
    @return: A boolean 
    """ 
    def wordBreak(self, s, wordSet): 
        if not s: 
            return True 
 
        n = len(s) 
        dp = [False] * (n + 1) 
        dp[0] = True 
 
        max_length = max([ 
            len(word) 
            for word in wordSet 
        ]) if wordSet else 0 
 
        for i in range(1, n + 1): 
            for l in range(1, max_length + 1): 
                if i < l: 
                    break 
                if not dp[i - l]: 
                    continue 
                word = s[i - l:i] 
                if word in wordSet: 
                    dp[i] = True 
                    break 
 
        return dp[n] 
 

堆 Heap

使用条件

  1. 找最大值或者最小值(60%)
  2. 找第 k 大(pop k 次 复杂度O(nlogk))(50%)
  3. 要求 logn 时间对数据进行操作(40%)

堆不能解决的问题

  1. 查询比某个数大的最小值/最接近的值(平衡排序二叉树 Balanced BST 才可以解决)
  2. 找某段区间的最大值最小值(线段树 SegmentTree 可以解决)
  3. O(n)找第k大 (使用快排中的partition操作)

领扣例题

LintCode 1274. 查找和最小的K对数字

用最小堆存放candidates到索引对。

每次从堆里poll出一个序列对,把后续的两个序列对放到堆里。

最小到必然是(0, 0)

后续是(0, 1), (1, 0), 分别对应{nums1[0], nums2[1]} 和 {nums1[1], nums2[0]}

(p[0], p[1]) 的后续是 {p[0]+1, p[1]} 和{p[0], p[1]+1}

时间复杂度是O(klogk)

public class Solution { 
    /** 
     * @param nums1: List[int] 
     * @param nums2: List[int] 
     * @param k: an integer 
     * @return: return List[List[int]] 
     */ 
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { 
        // write your code here 
        List<List<Integer>> ans = new ArrayList<>(); 
        HashSet<Integer> visited = new HashSet<>(); 
        PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> nums1[a[0]] + nums2[a[1]] == nums1[b[0]] + nums2[b[1]] ? nums1[a[0]] - nums1[b[0]] : nums1[a[0]] + nums2[a[1]] - nums1[b[0]] - nums2[b[1]]); 
        heap.offer(new int[] {0, 0}); 
        visited.add(0); 
        while (!heap.isEmpty() && ans.size() < k) { 
            int[] p = heap.poll(); 
            ans.add(Arrays.asList(nums1[p[0]], nums2[p[1]])); 
            if (p[0] + 1 < nums1.length) { 
                if (!visited.contains((p[0] + 1) * nums2.length + p[1])) { 
                    heap.offer(new int[] {p[0] + 1, p[1]}); 
                    visited.add((p[0] + 1) * nums2.length + p[1]); 
                } 
            } 
            if (p[1] + 1 < nums2.length) { 
                if (!visited.contains(p[0] * nums2.length + p[1] + 1)) { 
                    heap.offer(new int[] {p[0], p[1] + 1}); 
                    visited.add(p[0] * nums2.length + p[1] + 1); 
                } 
            } 
        } 
 
        return ans; 
    } 
}  

LintCode 919. 会议室 II

用前缀和算法,开一个数组,对每个interval开始位置+1,对每个结束位置-1,然后做数组前缀和,答案是前缀和最大值

/** 
 * Definition for an interval. 
 * public class Interval { 
 *     int start; 
 *     int end; 
 *     Interval() { start = 0; end = 0; } 
 *     Interval(int s, int e) { start = s; end = e; } 
 * } 
 */ 
 
class Point{ 
    int time; 
    int flag; 
 
    Point(int t, int s){ 
      this.time = t; 
      this.flag = s; 
    } 
    public static Comparator<Point> PointComparator  = new Comparator<Point>(){ 
      public int compare(Point p1, Point p2){ 
        if(p1.time == p2.time) return p1.flag - p2.flag; 
        else return p1.time - p2.time; 
      } 
    }; 
} 
public class Solution { 
    public int minMeetingRooms(Interval[] intervals) { 
        List<Point> list = new ArrayList<>(intervals.length*2); 
        for(Interval i : intervals){ 
          list.add(new Point(i.start, 1)); 
          list.add(new Point(i.end, 0)); 
        } 
 
        Collections.sort(list,Point.PointComparator ); 
        int count = 0, ans = 0; 
        for(Point p : list){ 
            if(p.flag == 1) { 
                count++; 
            } 
            else { 
                count--; 
            } 
            ans = Math.max(ans, count); 
        } 
 
        return ans; 
    } 
} 
 

LintCode 1512.雇佣K个人的最低费用

对每一组的quality和wage计算时薪,按照时薪排序。

对于合法的k个数,为了满足条件,每个人的工资都大于初始工资,且成比例,所以每个人的时薪一定是这k个人中最高的那个人的时薪(否则最高的人不满足初始工资)。

所以对每个人的时薪找k个quality和最小的且时薪低于这个人的即可。

利用优先队列即可完成上述操作。

import heapq 
class Solution: 
    """ 
    @param quality: an array 
    @param wage: an array 
    @param K: an integer 
    @return: the least amount of money needed to form a paid group 
    """ 
    def mincostToHireWorkers(self, quality, wage, K): 
        # Write your code here 
        workers = sorted([float(w) / q, q] for w, q in zip(wage, quality)) 
        res = float('inf') 
        qsum = 0 
        heap = [] 
        for r, q in workers: 
            heapq.heappush(heap, -q) 
            qsum += q 
            if len(heap) > K: qsum += heapq.heappop(heap) 
            if len(heap) == K: res = min(res, qsum * r) 
        return res 
 

代码模板

Java 带删除特定元素功能的堆

v2-7f998b80b3654a9843a74b376f7a1d37_720w.jpgv2-6afdf4f77a8702f198b99f8618a4a174_720w.jpg

Python 带删除特定元素功能的堆

v2-dd0b0b55a6406009065130e185d61549_720w.jpg

学姐的分享就到这里啦,关于并查集、字典树等其他算法模板,可移步《九章算法班》获取。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK