diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 24e9325..daddda7 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -11,34 +11,17 @@ on: jobs: bot: - # 运行环境,Ubuntu-20.04 - runs-on: Ubuntu-20.04 + runs-on: ubuntu-latest steps: # 检测代码 - name: 'Checkout codes' uses: actions/checkout@v2 - + # 修改系统时区 - name: Setup the time zone to Asia/Shanghai uses: zcong1993/setup-timezone@master with: timezone: Asia/Shanghai - - # 安装Python3.6 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - # 清除pip缓存 - - name: Configure pip cache - uses: actions/cache@v1 - id: pip-cache - with: - path: venv - key: pip-1-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - pip- # 安装requirements.txt中的依赖包 - name: Install dependencies @@ -50,9 +33,9 @@ jobs: # 运行leetcode.py文件 # 将在程序当前生成目录下生成一个新的README.md文件 - name: Get LeetCode Data - run: python leetcode.py ${{ secrets.LEETCODE_EMAIL }} ${{ secrets.LEETCODE_PASSWORD }} + run: python leetcode.py ${{ secrets.LEETCODE_SESSION }} # 执行push操作,推送最新的README.md文件 - uses: stefanzweifel/git-auto-commit-action@v4 with: - commit_message: Update changes \ No newline at end of file + commit_message: Update changes diff --git "a/Array/238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" "b/Array/238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" new file mode 100644 index 0000000..5416c48 --- /dev/null +++ "b/Array/238. \351\231\244\350\207\252\350\272\253\344\273\245\345\244\226\346\225\260\347\273\204\347\232\204\344\271\230\347\247\257.md" @@ -0,0 +1,93 @@ +#### 238. 除自身以外数组的乘积 + +难度:中等 + +--- + +给你一个整数数组 `nums`,返回 数组 `answer` ,其中 `answer[i]` 等于 `nums` 中除 `nums[i]` 之外其余各元素的乘积 。 + +题目数据 **保证** 数组 `nums`之中任意元素的全部前缀元素和后缀的乘积都在  **32 位** 整数范围内。 + +请  **不要使用除法,** 且在 `O(n)` 时间复杂度内完成此题。 + + **示例 1:** + +``` +输入: nums = [1,2,3,4] +输出: [24,12,8,6] +``` + + **示例 2:** + +``` +输入: nums = [-1,1,0,-3,3] +输出: [0,0,9,0,0] +``` + + **提示:** + +* `2 <= nums.length <= 10^5` +* `-30 <= nums[i] <= 30` +* **保证** 数组 `nums`之中任意元素的全部前缀元素和后缀的乘积都在  **32 位** 整数范围内 + + **进阶:** 你可以在 `O(1)` 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组  **不被视为**  额外空间。) + +--- + +法一: + +利用两个数组分别从左往右/从右往左遍历,记录除当前下标值的前缀/后缀乘积,注意前缀数组的第一个值为一,后缀数组的最后一个值为一,最后再两者相乘即是输出数组 + +```go +func productExceptSelf(nums []int) []int { + n := len(nums) + leftArray, rightArray, output := make([]int, n), make([]int, n), make([]int, n) + for i := 0; i < n; i++ { + if i == 0 { + leftArray[i] = 1 + } else { + leftArray[i] = leftArray[i - 1] * nums[i - 1] + } + } + for i := n - 1; i >= 0; i-- { + if i == n - 1 { + rightArray[i] = 1 + } else { + rightArray[i] = rightArray[i + 1] * nums[i + 1] + } + } + for i := 0; i < n; i++ { + output[i] = leftArray[i] * rightArray[i] + } + + return output +} +``` + + + +法二: + +先在 `output` 输出数组上从左往右累乘,再从右往左累乘,用一个临时遍历记录从右往左累乘的数。 + +```Go +func productExceptSelf(nums []int) []int { + n := len(nums) + output := make([]int, n) + for i := 0; i < n; i++ { + if i == 0 { + output[i] = 1 + } else { + output[i] = output[i - 1] * nums[i - 1] + } + } + temp := 1 + for i := n - 1; i >= 0; i-- { + if i != n - 1 { + output[i] *= temp + } + temp *= nums[i] + } + return output +} +``` \ No newline at end of file diff --git "a/Backtrace/39. \347\273\204\345\220\210\346\200\273\345\222\214.md" "b/Backtrace/39. \347\273\204\345\220\210\346\200\273\345\222\214.md" new file mode 100644 index 0000000..cf3d258 --- /dev/null +++ "b/Backtrace/39. \347\273\204\345\220\210\346\200\273\345\222\214.md" @@ -0,0 +1,132 @@ +#### 39. 组合总和 + +难度:中等 + +--- + +给你一个 **无重复元素** 的整数数组 `candidates` 和一个目标整数 `target` ,找出 `candidates` 中可以使数字和为目标数 `target` 的 所有  **不同组合** ,并以列表形式返回。你可以按 **任意顺序** 返回这些组合。 + +`candidates` 中的 **同一个** 数字可以 **无限制重复被选取** 。如果至少一个数字的被选数量不同,则两种组合是不同的。  + +对于给定的输入,保证和为 `target` 的不同组合数少于 `150` 个。 + + **示例 1:** + +``` +输入:candidates = [2,3,6,7], target = 7 +输出:[[2,2,3],[7]] +解释: +2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 +7 也是一个候选, 7 = 7 。 +仅有这两种组合。 +``` + + **示例 2:** + +``` +输入: candidates = [2,3,5], target = 8 +输出: [[2,2,2,2],[2,3,3],[3,5]] +``` + + **示例 3:** + +``` +输入: candidates = [2], target = 1 +输出: [] +``` + + **提示:** + +* `1 <= candidates.length <= 30` +* `2 <= candidates[i] <= 40` +* `candidates` 的所有元素 **互不相同** +* `1 <= target <= 40` + +--- + +**方法一:枚举选哪个** + +从 `target` 开始,每次回溯都减或不减当前下标的值。本题用到剪枝在于先将数组按升序排序,如果当前下标的值小于 `target` 了,那么后续也不需要遍历了。 + +但与全排列不同的是,本题并不需要用到 `visited` 数组,而是需要记录下标的 `index` 变量: + +- 排列问题,需要考虑顺序时,使用是否遍历过了的 `visited` 数组 +- 组合问题,不考虑顺序时,使用记录下标的 `index` 变量 + +```Java +class Solution { + public List> combinationSum(int[] candidates, int target) { + List> res = new ArrayList<>(); + Arrays.sort(candidates); + backtrace(res, new ArrayList<>(), candidates, 0, target); + return res; + } + + private void backtrace(List> res, List temp, int[] candidates, int index, int target){ + if(target == 0){ + res.add(new ArrayList<>(temp)); + return; + } + for(int i = index; i < candidates.length; i++){ + if(target - candidates[i] < 0) return; + temp.add(candidates[i]); + backtrace(res, temp, candidates, i, target - candidates[i]); + temp.remove(temp.size() - 1); + } + } +} +``` + +Go + +```go +func combinationSum(candidates []int, target int) [][]int { + res := [][]int{} + var dfs func(index, remain int, temp []int) + dfs = func(index, remain int, temp []int){ + if remain == 0 { + res = append(res, slices.Clone(temp)) // 一定要复制一份 + return + } + if remain < 0 { + return + } + for i := index; i < len(candidates); i++ { + temp = append(temp, candidates[i]) + dfs(i, remain - candidates[i], temp) + temp = temp[:len(temp) - 1] + } + } + dfs(0, target, []int{}) + return res +} +``` + + + + + +**方法二:选或不选** + +```go +func combinationSum(candidates []int, target int) [][]int { + res := [][]int{} + var dfs func(index, remain int, temp []int) + dfs = func(index, remain int, temp []int){ + if index >= len(candidates) || remain < 0 { + return + } + if remain == 0 { + res = append(res, slices.Clone(temp)) + return + } + dfs(index + 1, remain, temp) + temp = append(temp, candidates[index]) + dfs(index, remain - candidates[index], temp) + temp = temp[:len(temp) - 1] + } + dfs(0, target, []int{}) + return res +} +``` + diff --git "a/Backtrace/46. \345\205\250\346\216\222\345\210\227.md" "b/Backtrace/46. \345\205\250\346\216\222\345\210\227.md" new file mode 100644 index 0000000..18e7777 --- /dev/null +++ "b/Backtrace/46. \345\205\250\346\216\222\345\210\227.md" @@ -0,0 +1,70 @@ +#### 46. 全排列 + +难度:中等 + +--- + +给定一个不含重复数字的数组 `nums` ,返回其 _所有可能的全排列_ 。你可以 **按任意顺序** 返回答案。 + + **示例 1:** + +``` +输入:nums = [1,2,3] +输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +``` + + **示例 2:** + +``` +输入:nums = [0,1] +输出:[[0,1],[1,0]] +``` + + **示例 3:** + +``` +输入:nums = [1] +输出:[[1]] +``` + + **提示:** + +* `1 <= nums.length <= 6` +* `-10 <= nums[i] <= 10` +* `nums` 中的所有整数 **互不相同** + +--- + +回溯: + +需要一个辅助数组记录是否已经被回溯过。复杂度会比较高。 + +时间复杂度:$O(N!N)$,$N$ 为数组长度。 + +```Java +class Solution { + public List> permute(int[] nums) { + int n = nums.length; + boolean[] visited = new boolean[n]; + List> res = new ArrayList<>(); + bactrack(nums, res, new ArrayList(), visited); + return res; + } + + private void bactrack(int[] nums, List> res, ArrayList temp, boolean[] visited){ + if(temp.size() == visited.length){ + res.add(new ArrayList<>(temp)); + return; + } + for(int i = 0; i < visited.length; i++){ + if(visited[i] == true) continue; + visited[i] = true; + temp.add(nums[i]); + bactrack(nums, res, temp, visited); + visited[i] = false; + temp.remove(temp.size() - 1); + } + + } +} +``` \ No newline at end of file diff --git "a/Backtrace/47. \345\205\250\346\216\222\345\210\227 II.md" "b/Backtrace/47. \345\205\250\346\216\222\345\210\227 II.md" new file mode 100644 index 0000000..48f8d8a --- /dev/null +++ "b/Backtrace/47. \345\205\250\346\216\222\345\210\227 II.md" @@ -0,0 +1,63 @@ +#### 47. 全排列 II + +难度:中等 + +--- + +给定一个可包含重复数字的序列 `nums` ,_**按任意顺序**_ 返回所有不重复的全排列。 + + **示例 1:** + +``` +输入:nums = [1,1,2] +输出: +[[1,1,2], + [1,2,1], + [2,1,1]] +``` + + **示例 2:** + +``` +输入:nums = [1,2,3] +输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +``` + + **提示:** + +* `1 <= nums.length <= 8` +* `-10 <= nums[i] <= 10` + +--- + +回溯: + +与[全排列](https://leetcode.cn/problems/permutations-ii/)不同的是,本题需要进行去重操作。具体见代码。 + +```Java +class Solution { + public List> permuteUnique(int[] nums) { + Arrays.sort(nums); + List> res = new ArrayList<>(); + backtrace(nums, res, new ArrayList(), new boolean[nums.length]); + return res; + } + + private void backtrace(int[] nums, List> res, ArrayList temp, boolean[] visited){ + if(nums.length == temp.size()){ + res.add(new ArrayList<>(temp)); + return; + } + for(int i = 0; i < nums.length; i++){ + if(visited[i] || (i > 0 && nums[i] == nums[i - 1] && !visited[i] && visited[i - 1])){ // 去重 + continue; + } + visited[i] = true; + temp.add(nums[i]); + backtrace(nums, res, temp, visited); + visited[i] = false; + temp.remove(temp.size() - 1); + } + } +} +``` \ No newline at end of file diff --git "a/Backtrace/77. \347\273\204\345\220\210.md" "b/Backtrace/77. \347\273\204\345\220\210.md" new file mode 100644 index 0000000..c870a70 --- /dev/null +++ "b/Backtrace/77. \347\273\204\345\220\210.md" @@ -0,0 +1,64 @@ +#### 77. 组合 + +难度:中等 + +--- + +给定两个整数 `n` 和 `k`,返回范围 `[1, n]` 中所有可能的 `k` 个数的组合。 + +你可以按 **任何顺序** 返回答案。 + + **示例 1:** + +``` +输入:n = 4, k = 2 +输出: +[ + [2,4], + [3,4], + [2,3], + [1,2], + [1,3], + [1,4], +] +``` + + **示例 2:** + +``` +输入:n = 1, k = 1 +输出:[[1]] +``` + + **提示:** + +* `1 <= n <= 20` +* `1 <= k <= n` + +--- + +回溯 + 记忆化搜索: + +使用一个 `temp` 数组记录搜索过的数字。简单回溯。注意 `res.add(new ArrayList<>(temp));`。 + +```Java +class Solution { + public List> combine(int n, int k) { + List> res = new ArrayList<>(); + backtrace(res, new ArrayList(), n, k, 1); + return res; + } + + private void backtrace(List> res, List temp, int n, int k, int index){ + if(temp.size() == k){ + res.add(new ArrayList<>(temp)); + return; + } + if(index > n) return; + temp.add(index); + backtrace(res, temp, n, k, index + 1); + temp.remove(temp.size() - 1); + backtrace(res, temp, n, k, index + 1); + } +} +``` \ No newline at end of file diff --git "a/Breadth First Search/1376. \351\200\232\347\237\245\346\211\200\346\234\211\345\221\230\345\267\245\346\211\200\351\234\200\347\232\204\346\227\266\351\227\264.md" "b/Breadth First Search/1376. \351\200\232\347\237\245\346\211\200\346\234\211\345\221\230\345\267\245\346\211\200\351\234\200\347\232\204\346\227\266\351\227\264.md" new file mode 100644 index 0000000..1f319f0 --- /dev/null +++ "b/Breadth First Search/1376. \351\200\232\347\237\245\346\211\200\346\234\211\345\221\230\345\267\245\346\211\200\351\234\200\347\232\204\346\227\266\351\227\264.md" @@ -0,0 +1,82 @@ +#### 1376. 通知所有员工所需的时间 + +难度:中等 + +--- + +公司里有 `n` 名员工,每个员工的 ID 都是独一无二的,编号从 `0` 到 `n - 1`。公司的总负责人通过 `headID` 进行标识。 + +在 `manager` 数组中,每个员工都有一个直属负责人,其中 `manager[i]` 是第 `i` 名员工的直属负责人。对于总负责人,`manager[headID] = -1`。题目保证从属关系可以用树结构显示。 + +公司总负责人想要向公司所有员工通告一条紧急消息。他将会首先通知他的直属下属们,然后由这些下属通知他们的下属,直到所有的员工都得知这条紧急消息。 + +第 `i` 名员工需要 `informTime[i]` 分钟来通知它的所有直属下属(也就是说在 `informTime[i]` 分钟后,他的所有直属下属都可以开始传播这一消息)。 + +返回通知所有员工这一紧急消息所需要的 **分钟数** 。 + + **示例 1:** + +``` +输入:n = 1, headID = 0, manager = [-1], informTime = [0] +输出:0 +解释:公司总负责人是该公司的唯一一名员工。 +``` + + **示例 2:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/03/08/graph.png) + +``` +输入:n = 6, headID = 2, manager = [2,2,-1,2,2,2], informTime = [0,0,1,0,0,0] +输出:1 +解释:id = 2 的员工是公司的总负责人,也是其他所有员工的直属负责人,他需要 1 分钟来通知所有员工。 +上图显示了公司员工的树结构。 +``` + + **提示:** + +* `1 <= n <= 10^5` +* `0 <= headID < n` +* `manager.length == n` +* `0 <= manager[i] < n` +* `manager[headID] == -1` +* `informTime.length == n` +* `0 <= informTime[i] <= 1000` +* 如果员工 `i` 没有下属,`informTime[i] == 0` 。 +* 题目 **保证** 所有员工都可以收到通知。 + +--- + +广度优先搜索: + +由原 `manager` 数组构造一个邻接表,再构造一对 `` ,最后只需要取最大的 `time` 即可。 + +```Java +class Solution { + public int numOfMinutes(int n, int headID, int[] manager, int[] informTime) { + int res = 0; + List[] adj = new List[n]; + for(int i = 0; i < n; i++){ + adj[i] = new ArrayList(); + } + for(int i = 0; i < n; i++){ + if(manager[i] != -1){ + adj[manager[i]].add(i); + } + } + ArrayDeque> queue = new ArrayDeque>(); + queue.add(new Pair<>(headID, 0)); + while(!queue.isEmpty()){ + int sz = queue.size(); + for(int i = 0; i < sz; i++){ + Pair temp = queue.poll(); + res = Math.max(res, temp.getValue()); + for(int j = 0; j < adj[temp.getKey()].size(); j++){ + queue.add(new Pair<>(adj[temp.getKey()].get(j), temp.getValue() + informTime[temp.getKey()])); + } + } + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Breadth First Search/958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" "b/Breadth First Search/958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" new file mode 100644 index 0000000..e971195 --- /dev/null +++ "b/Breadth First Search/958. \344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.md" @@ -0,0 +1,69 @@ +#### 958. 二叉树的完全性检验 + +难度:中等 + +--- + +给你一棵二叉树的根节点 `root` ,请你判断这棵树是否是一棵 **完全二叉树**  。 + +在一棵 **[完全二叉树](https://baike.baidu.com/item/完全二叉树/7773232?fr=aladdin)** 中,除了最后一层外,所有层都被完全填满,并且最后一层中的所有节点都尽可能靠左。最后一层(第 `h` 层)中可以包含 `1` 到 `2^h` 个节点。 + + **示例 1:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-1.png) + +``` +输入:root = [1,2,3,4,5,6] +输出:true +解释:最后一层前的每一层都是满的(即,节点值为 {1} 和 {2,3} 的两层),且最后一层中的所有节点({4,5,6})尽可能靠左。 +``` + + **示例 2:** + + **![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/15/complete-binary-tree-2.png)** + +``` +输入:root = [1,2,3,4,5,null,7] +输出:false +解释:值为 7 的节点不满足条件「节点尽可能靠左」。 +``` + + **提示:** + +* 树中节点数目在范围 `[1, 100]` 内 +* `1 <= Node.val <= 1000` + +--- + +广度搜索优先: + +层序遍历将所有节点(包括空节点)放入队列中,并且检查队列中的节点是否是空节点然后弹出,如果遇到空节点并且后续有节点再插入到队列中,意味着肯定为非完全二叉树。 + +```Go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isCompleteTree(root *TreeNode) bool { + flag := false + queue := []*TreeNode{root} + for len(queue) > 0 { + node := queue[0] + queue = queue[1:] + if node == nil { + flag = true + } else { + if flag == true { + return false + } + queue = append(queue, node.Left) + queue = append(queue, node.Right) + } + } + return true +} +``` \ No newline at end of file diff --git "a/Depth First Search/105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" "b/Depth First Search/105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 0000000..8a54442 --- /dev/null +++ "b/Depth First Search/105. \344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,61 @@ +#### 105. 从前序与中序遍历序列构造二叉树 + +难度:中等 + +--- + +给定两个整数数组 `preorder` 和 `inorder` ,其中 `preorder` 是二叉树的 **先序遍历** , `inorder` 是同一棵树的 **中序遍历** ,请构造二叉树并返回其根节点。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) +``` +输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] +输出: [3,9,20,null,null,15,7] +``` + + **示例 2:** + +``` +输入: preorder = [-1], inorder = [-1] +输出: [-1] +``` + + **提示:** + +* `1 <= preorder.length <= 3000` +* `inorder.length == preorder.length` +* `-3000 <= preorder[i], inorder[i] <= 3000` +* `preorder` 和 `inorder` 均 **无重复** 元素 +* `inorder` 均出现在 `preorder` +* `preorder`  **保证** 为二叉树的前序遍历序列 +* `inorder`  **保证** 为二叉树的中序遍历序列 + +--- + +深度搜索优先: + +每次递归对前序序列和中序序列做切片。具体切到什么位置,拿示例推理一遍。 + +```Go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func buildTree(preorder []int, inorder []int) *TreeNode { + for i := range inorder { + if inorder[i] == preorder[0] { + return &TreeNode{ + Val: inorder[i], + Left: buildTree(preorder[1:i+1], inorder[:i]), + Right: buildTree(preorder[i+1:], inorder[i+1:]), + } + } + } + return nil +} +``` \ No newline at end of file diff --git "a/Depth First Search/106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" "b/Depth First Search/106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 0000000..f08d8bf --- /dev/null +++ "b/Depth First Search/106. \344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,62 @@ +#### 106. 从中序与后序遍历序列构造二叉树 + +难度:中等 + +--- + +给定两个整数数组 `inorder` 和 `postorder` ,其中 `inorder` 是二叉树的中序遍历, `postorder` 是同一棵树的后序遍历,请你构造并返回这颗 _二叉树_ 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) +``` +输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] +输出:[3,9,20,null,null,15,7] +``` + + **示例 2:** + +``` +输入:inorder = [-1], postorder = [-1] +输出:[-1] +``` + + **提示:** + +* `1 <= inorder.length <= 3000` +* `postorder.length == inorder.length` +* `-3000 <= inorder[i], postorder[i] <= 3000` +* `inorder` 和 `postorder` 都由 **不同** 的值组成 +* `postorder` 中每一个值都在 `inorder` 中 +* `inorder`  **保证** 是树的中序遍历 +* `postorder`  **保证** 是树的后序遍历 + +--- + +深度搜索优先: + +每次递归对中序序列和后序序列做切片。具体切到什么位置,拿示例推理一遍。 + +```Go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func buildTree(inorder []int, postorder []int) *TreeNode { + end := len(postorder) - 1 + for i := range inorder { + if inorder[i] == postorder[end] { + return &TreeNode { + Val: inorder[i], + Left: buildTree(inorder[:i], postorder[:i]), + Right: buildTree(inorder[i+1:], postorder[i:end]), + } + } + } + return nil +} +``` \ No newline at end of file diff --git "a/Depth First Search/131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" "b/Depth First Search/131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" new file mode 100644 index 0000000..026e05f --- /dev/null +++ "b/Depth First Search/131. \345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.md" @@ -0,0 +1,68 @@ +#### 131. 分割回文串 + +难度:中等 + +--- + +给你一个字符串 `s`,请你将 `s` 分割成一些子串,使每个子串都是 + + **回文串** + +。返回 `s` 所有可能的分割方案。 + + **示例 1:** + +``` +输入:s = "aab" +输出:[["a","a","b"],["aa","b"]] +``` + + **示例 2:** + +``` +输入:s = "a" +输出:[["a"]] +``` + + **提示:** + +* `1 <= s.length <= 16` +* `s` 仅由小写英文字母组成 + +--- + +回溯: + +```Go +func partition(s string) (ans [][]string) { + n := len(s) + path := []string{} + var dfs func(int) + dfs = func(i int) { + if i == n { + ans = append(ans, append([]string(nil), path...)) + return + } + for j := i; j < n; j++ { // 枚举子串的结束位置 + if isPalindrome(s, i, j) { + path = append(path, s[i : j + 1]) + dfs(j + 1) + path = path[:len(path) - 1] + } + } + } + dfs(0) + return +} + +func isPalindrome(s string, left, right int) bool { + for left < right { + if s[left] != s[right] { + return false + } + left++ + right-- + } + return true +} +``` \ No newline at end of file diff --git "a/Depth First Search/200. \345\262\233\345\261\277\346\225\260\351\207\217.md" "b/Depth First Search/200. \345\262\233\345\261\277\346\225\260\351\207\217.md" new file mode 100644 index 0000000..756337e --- /dev/null +++ "b/Depth First Search/200. \345\262\233\345\261\277\346\225\260\351\207\217.md" @@ -0,0 +1,83 @@ +#### 200. 岛屿数量 + +难度:中等 + +--- + +给你一个由 `'1'`(陆地)和 `'0'`(水)组成的的二维网格,请你计算网格中岛屿的数量。 + +岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 + +此外,你可以假设该网格的四条边均被水包围。 + + **示例 1:** + +``` +输入:grid = [ + ["1","1","1","1","0"], + ["1","1","0","1","0"], + ["1","1","0","0","0"], + ["0","0","0","0","0"] +] +输出:1 +``` + + **示例 2:** + +``` +输入:grid = [ + ["1","1","0","0","0"], + ["1","1","0","0","0"], + ["0","0","1","0","0"], + ["0","0","0","1","1"] +] +输出:3 +``` + + **提示:** + +* `m == grid.length` +* `n == grid[i].length` +* `1 <= m, n <= 300` +* `grid[i][j]` 的值为 `'0'` 或 `'1'` + +--- + +深度搜索优先: + +遍历每一格数据,如果当前格数据为 `1`,则岛屿数量加一,并且将四周(上下左右)的 `1` 变为 `0`,不断扩散(深度搜索) + +```Go +func numIslands(grid [][]byte) int { + res := 0 + var dfs func(grid [][]byte, i, j int) + dfs = func(grid [][]byte, i, j int) { + if grid[i][j] == '0' { + return + } + m, n := len(grid), len(grid[0]) + grid[i][j] = '0' + if i > 0 { + dfs(grid, i - 1, j) + } + if i < m - 1 { + dfs(grid, i + 1, j) + } + if j > 0 { + dfs(grid, i, j - 1) + } + if j < n - 1 { + dfs(grid, i, j + 1) + } + } + for i := 0; i < len(grid); i++ { + for j := 0; j < len(grid[0]); j++ { + if grid[i][j] == '1' { + dfs(grid, i, j) + res ++ + } + } + } + return res +} +``` \ No newline at end of file diff --git "a/Depth First Search/22. \346\213\254\345\217\267\347\224\237\346\210\220.md" "b/Depth First Search/22. \346\213\254\345\217\267\347\224\237\346\210\220.md" new file mode 100644 index 0000000..82e2693 --- /dev/null +++ "b/Depth First Search/22. \346\213\254\345\217\267\347\224\237\346\210\220.md" @@ -0,0 +1,56 @@ +#### 22. 括号生成 + +难度:中等 + +--- + +数字 `n` 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 **有效的** 括号组合。 + + **示例 1:** + +``` +输入:n = 3 +输出:["((()))","(()())","(())()","()(())","()()()"] +``` + + **示例 2:** + +``` +输入:n = 1 +输出:["()"] +``` + + **提示:** + +* `1 <= n <= 8` + +--- + +深度优先搜索: + +用两个变量分别记录左/右括号剩余的数量,显然,左括号剩余的数量一定不能大于右括号剩余的数量。 + +只要左括号剩余的数量大于零,就要将该数量减一并继续递归。只有当右括号剩余的数量大于左括号剩余的数量时,才继续递归。 + +```Go +func generateParenthesis(n int) []string { + res := []string{} + var dfs func(num1, num2 int, s string) + dfs = func(num1, num2 int, s string) { + if len(s) == 2 * n { + res = append(res, s) + return + } + if num1 > 0 { + dfs(num1 - 1, num2, s + "(") // 类似于回溯,此处不对字符串 s 本身做修改,所以不需要新增字符串再删除字符串的操作 + } + if num1 < num2 { + dfs(num1, num2 - 1, s + ")") + } + } + dfs(n, n, "") + return res +} + + +``` \ No newline at end of file diff --git "a/Depth First Search/2487. \344\273\216\351\223\276\350\241\250\344\270\255\347\247\273\351\231\244\350\212\202\347\202\271.md" "b/Depth First Search/2487. \344\273\216\351\223\276\350\241\250\344\270\255\347\247\273\351\231\244\350\212\202\347\202\271.md" new file mode 100644 index 0000000..54ee071 --- /dev/null +++ "b/Depth First Search/2487. \344\273\216\351\223\276\350\241\250\344\270\255\347\247\273\351\231\244\350\212\202\347\202\271.md" @@ -0,0 +1,69 @@ +#### 2487. 从链表中移除节点 + +难度:中等 + +--- + +给你一个链表的头节点 `head` 。 + +移除每个右侧有一个更大数值的节点。 + +返回修改后链表的头节点 `head` 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2022/10/02/drawio.png) + +``` +输入:head = [5,2,13,3,8] +输出:[13,8] +解释:需要移除的节点是 5 ,2 和 3 。 +- 节点 13 在节点 5 右侧。 +- 节点 13 在节点 2 右侧。 +- 节点 8 在节点 3 右侧。 +``` + + **示例 2:** + +``` +输入:head = [1,1,1,1] +输出:[1,1,1,1] +解释:每个节点的值都是 1 ,所以没有需要移除的节点。 +``` + + **提示:** + +* 给定列表中的节点数目在范围 `[1, 10^5]` 内 +* `1 <= Node.val <= 10^5` + +--- +DFS: + +由题意可知,节点对它右侧的所有节点都没有影响,因此对于某一节点,我们可以**对它的右侧节点递归地进行移除操作**: + +- 该节点为空,那么递归函数返回空指针。 + +- 该节点不为空,那么先对它的右侧节点进行移除操作,得到一个新的子链表,如果子链表的表头节点值大于该节点的值,那么移除该节点,否则将该节点作为子链表的表头节点,最后返回该子链表。 + + + +```Go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func removeNodes(head *ListNode) *ListNode { + if head == nil { + return head + } + head.Next = removeNodes(head.Next) + if head.Next != nil && head.Val < head.Next.Val { + return head.Next + } else { + return head + } +} +``` \ No newline at end of file diff --git "a/Depth First Search/337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" "b/Depth First Search/337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" new file mode 100644 index 0000000..7af77e7 --- /dev/null +++ "b/Depth First Search/337. \346\211\223\345\256\266\345\212\253\350\210\215 III.md" @@ -0,0 +1,77 @@ +#### 337. 打家劫舍 III + +难度:中等 + +--- + +小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 `root` 。 + +除了 `root` 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 **两个直接相连的房子在同一天晚上被打劫** ,房屋将自动报警。 + +给定二叉树的 `root` 。返回 _ **在不触动警报的情况下**  ,小偷能够盗取的最高金额_ 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/03/10/rob1-tree.jpg) + +``` +输入: root = [3,2,3,null,3,null,1] +输出: 7 +解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7 +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/03/10/rob2-tree.jpg) + +``` +输入: root = [3,4,5,1,3,null,1] +输出: 9 +解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9 +``` + + **提示:** + +* 树的节点数在 `[1, 10^4]` 范围内 +* `0 <= Node.val <= 10^4` + +--- + +深度优先搜索: + +假设当前节点不选择,那么当前节点的最大盗取金额为左节点的最大值(不管左节点选还是不选)和右节点的最大值(同样的,不管右节点选还是不选)。 + +递归方法的返回类型应为长度为 `2` 的数组,第一个元素表示选当前节点的最大值,第二个元素表示不选当前节点的最大值。 + +```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 int rob(TreeNode root) { + int[] array = dfs(root); + return Math.max(array[0], array[1]); + } + + private int[] dfs(TreeNode root){ + if(root == null) return new int[]{0, 0}; + int[] left = dfs(root.left); + int[] right = dfs(root.right); + int selected = root.val + left[1] + right[1]; + int notSelected = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); + return new int[]{selected, notSelected}; + } +} +``` \ No newline at end of file diff --git "a/Depth First Search/784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" "b/Depth First Search/784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" new file mode 100644 index 0000000..96cbee3 --- /dev/null +++ "b/Depth First Search/784. \345\255\227\346\257\215\345\244\247\345\260\217\345\206\231\345\205\250\346\216\222\345\210\227.md" @@ -0,0 +1,89 @@ +#### 784. 字母大小写全排列 + +难度:中等 + +--- + +给定一个字符串 `s` ,通过将字符串 `s` 中的每个字母转变大小写,我们可以获得一个新的字符串。 + +返回 _所有可能得到的字符串集合_ 。以 **任意顺序** 返回输出。 + + **示例 1:** + +``` +输入:s = "a1b2" +输出:["a1b2", "a1B2", "A1b2", "A1B2"] +``` + + **示例 2:** + +``` +输入: s = "3z4" +输出: ["3z4","3Z4"] +``` + + **提示:** + +* `1 <= s.length <= 12` +* `s` 由小写英文字母、大写英文字母和数字组成 + +--- + +法一,深度优先搜索: + +如果当前字符是字母,则变换大小写再调用本身。 + +```Java +class Solution { + public List letterCasePermutation(String s) { + List res = new ArrayList<>(); + dfs(res, new StringBuilder(), s); + return res; + } + + private void dfs(List res, StringBuilder sb, String s){ + int index = sb.length(); + if(index == s.length()){ + res.add(sb.toString()); + return; + } + char c = s.charAt(index); + dfs(res, new StringBuilder(sb).append(c), s); + if(Character.isLetter(c)){ + c ^= ' '; // 大小写转换,或与32异或也可以实现 + dfs(res, new StringBuilder(sb).append(c), s); + } + + } +} +``` + +法二,回溯: + +与法一类似,如果当前字符是字母,则大小写转换后回溯,同时回溯后需要将字母变换回来。 + +```java +class Solution { + public List letterCasePermutation(String s) { + List res = new ArrayList<>(); + backtrace(res, new StringBuilder(), s); + return res; + } + + private void backtrace(List res, StringBuilder sb, String s){ + int index = sb.length(); + if(index == s.length()){ + res.add(sb.toString()); + return; + } + char c = s.charAt(index); + if(Character.isLetter(c)){ + c ^= 32; + backtrace(res, new StringBuilder(sb).append(c), s); + c ^= 32; + } + backtrace(res, new StringBuilder(sb).append(c), s); + } +} +``` + diff --git "a/Depth First Search/LCR 143. \345\255\220\347\273\223\346\236\204\345\210\244\346\226\255.md" "b/Depth First Search/LCR 143. \345\255\220\347\273\223\346\236\204\345\210\244\346\226\255.md" new file mode 100644 index 0000000..e77de62 --- /dev/null +++ "b/Depth First Search/LCR 143. \345\255\220\347\273\223\346\236\204\345\210\244\346\226\255.md" @@ -0,0 +1,63 @@ +#### LCR 143. 子结构判断 + +难度:中等 + +--- + +给定两棵二叉树 `tree1` 和 `tree2`,判断 `tree2` 是否以 `tree1` 的某个节点为根的子树具有 **相同的结构和节点值** 。 +注意, **空树**  不会是以 `tree1` 的某个节点为根的子树具有 **相同的结构和节点值** 。 + + **示例 1:** + +![](https://pic.leetcode.cn/1694684670-vwyIgY-two_tree.png) + +``` +输入:tree1 = [1,7,5], tree2 = [6,1] +输出:false +解释:tree2 与 tree1 的一个子树没有相同的结构和节点值。 +``` + + **示例 2:** + +![](https://pic.leetcode.cn/1694685602-myWXCv-two_tree_2.png) + +``` +输入:tree1 = [3,6,7,1,8], tree2 = [6,1] +输出:true +解释:tree2 与 tree1 的一个子树拥有相同的结构和节点值。即 6 - > 1。 +``` + + **提示:** + +`0 <= 节点个数 <= 10000` + +--- + +深度搜索优先: + +```Go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isSubStructure(A *TreeNode, B *TreeNode) bool { + if A == nil || B == nil { + return false + } + return dfs(A, B) || isSubStructure(A.Left, B) || isSubStructure(A.Right, B) +} + +func dfs(A *TreeNode, B *TreeNode) bool { + if B == nil { + return true + } + if A == nil || A .Val != B.Val { + return false + } + return dfs(A.Left, B.Left) && dfs(A.Right, B.Right) +} +``` \ No newline at end of file diff --git "a/Dichotomy/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" "b/Dichotomy/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" new file mode 100644 index 0000000..38bada2 --- /dev/null +++ "b/Dichotomy/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274.md" @@ -0,0 +1,77 @@ +#### 153. 寻找旋转排序数组中的最小值 + +难度:中等 + +--- + +已知一个长度为 `n` 的数组,预先按照升序排列,经由 `1` 到 `n` 次 **旋转** 后,得到输入数组。例如,原数组 `nums = [0,1,2,4,5,6,7]` 在变化后可能得到: + +* 若旋转 `4` 次,则可以得到 `[4,5,6,7,0,1,2]` +* 若旋转 `7` 次,则可以得到 `[0,1,2,4,5,6,7]` + +注意,数组 `[a[0], a[1], a[2], ..., a[n-1]]` **旋转一次** 的结果为数组 `[a[n-1], a[0], a[1], a[2], ..., a[n-2]]` 。 + +给你一个元素值 **互不相同** 的数组 `nums` ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 **最小元素** 。 + +你必须设计一个时间复杂度为 `O(log n)` 的算法解决此问题。 + + **示例 1:** + +``` +输入:nums = [3,4,5,1,2] +输出:1 +解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。 +``` + + **示例 2:** + +``` +输入:nums = [4,5,6,7,0,1,2] +输出:0 +解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。 +``` + + **示例 3:** + +``` +输入:nums = [11,13,15,17] +输出:11 +解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。 +``` + + **提示:** + +* `n == nums.length` +* `1 <= n <= 5000` +* `-5000 <= nums[i] <= 5000` +* `nums` 中的所有整数 **互不相同** +* `nums` 原来是一个升序排序的数组,并进行了 `1` 至 `n` 次旋转 + +--- + +二分查找: + +考虑以下三种情况 + +1. 左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界 +2. 左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界 +3. 左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界 + +因此选择中值与右值比较。 + +```Java +class Solution { + public int findMin(int[] nums) { + int left = 0, right = nums.length - 1; + while(left < right){ + int mid = (right + left) / 2; + if(nums[mid] < nums[right]){ + right = mid; + } else { + left = mid + 1; + } + } + return nums[left]; + } +} +``` \ No newline at end of file diff --git "a/Dichotomy/162. \345\257\273\346\211\276\345\263\260\345\200\274.md" "b/Dichotomy/162. \345\257\273\346\211\276\345\263\260\345\200\274.md" new file mode 100644 index 0000000..b9d320d --- /dev/null +++ "b/Dichotomy/162. \345\257\273\346\211\276\345\263\260\345\200\274.md" @@ -0,0 +1,72 @@ +#### 162. 寻找峰值 + +难度:中等 + +--- + +峰值元素是指其值严格大于左右相邻值的元素。 + +给你一个整数数组 `nums`,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 **任何一个峰值** 所在位置即可。 + +你可以假设 `nums[-1] = nums[n] = -∞` 。 + +你必须实现时间复杂度为 `O(log n)` 的算法来解决此问题。 + + **示例 1:** + +``` +输入:nums = [1,2,3,1] +输出:2 +解释:3 是峰值元素,你的函数应该返回其索引 2。 +``` + + **示例 2:** + +``` +输入:nums = [1,2,1,3,5,6,4] +输出:1 或 5 +解释:你的函数可以返回索引 1,其峰值元素为 2; +  或者返回索引 5, 其峰值元素为 6。 +``` + + **提示:** + +* `1 <= nums.length <= 1000` +* `-2^31 <= nums[i] <= 2^31 - 1` +* 对于所有有效的 `i` 都有 `nums[i] != nums[i + 1]` + +--- + +二分查找 + 爬坡理论: + +> 如果我们从一个位置开始,不断地向高处走,那么最终一定可以到达一个峰值位置。 + +基于爬坡理论可以确定一点: + +**如果当前元素不同于相邻元素时,那么沿着元素值递增的方向一定能找到一个峰值。** + +```Java +class Solution { + public int findPeakElement(int[] nums) { + int left = 0, right = nums.length - 1; + while(left <= right){ + int mid = (left + right) / 2; + if(compareLeft(nums, mid) && compareRight(nums, mid)){ + return mid; + } else if(compareLeft(nums, mid)) left = mid + 1; + else right = mid - 1; + } + return -1; + } + + private boolean compareLeft(int[] nums, int index){ + if(index == 0 || nums[index] > nums[index - 1]) return true; + return false; + } + + private boolean compareRight(int[] nums, int index){ + if(index == nums.length - 1 || nums[index] > nums[index + 1]) return true; + return false; + } +} +``` \ No newline at end of file diff --git "a/Dichotomy/2300. \345\222\222\350\257\255\345\222\214\350\215\257\346\260\264\347\232\204\346\210\220\345\212\237\345\257\271\346\225\260.md" "b/Dichotomy/2300. \345\222\222\350\257\255\345\222\214\350\215\257\346\260\264\347\232\204\346\210\220\345\212\237\345\257\271\346\225\260.md" new file mode 100644 index 0000000..e0cdc02 --- /dev/null +++ "b/Dichotomy/2300. \345\222\222\350\257\255\345\222\214\350\215\257\346\260\264\347\232\204\346\210\220\345\212\237\345\257\271\346\225\260.md" @@ -0,0 +1,77 @@ +#### 2300. 咒语和药水的成功对数 + +难度:中等 + +--- + +给你两个正整数数组 `spells` 和 `potions` ,长度分别为 `n` 和 `m` ,其中 `spells[i]` 表示第 `i` 个咒语的能量强度,`potions[j]` 表示第 `j` 瓶药水的能量强度。 + +同时给你一个整数 `success` 。一个咒语和药水的能量强度 **相乘** 如果  **大于等于**  `success` ,那么它们视为一对  **成功**  的组合。 + +请你返回一个长度为 `n` 的整数数组 `pairs`,其中 `pairs[i]` 是能跟第 `i` 个咒语成功组合的 **药水** 数目。 + + **示例 1:** + +``` +输入:spells = [5,1,3], potions = [1,2,3,4,5], success = 7 +输出:[4,0,3] +解释: +- 第 0 个咒语:5 * [1,2,3,4,5] = [5,10,15,20,25] 。总共 4 个成功组合。 +- 第 1 个咒语:1 * [1,2,3,4,5] = [1,2,3,4,5] 。总共 0 个成功组合。 +- 第 2 个咒语:3 * [1,2,3,4,5] = [3,6,9,12,15] 。总共 3 个成功组合。 +所以返回 [4,0,3] 。 +``` + + **示例 2:** + +``` +输入:spells = [3,1,2], potions = [8,5,8], success = 16 +输出:[2,0,2] +解释: +- 第 0 个咒语:3 * [8,5,8] = [24,15,24] 。总共 2 个成功组合。 +- 第 1 个咒语:1 * [8,5,8] = [8,5,8] 。总共 0 个成功组合。 +- 第 2 个咒语:2 * [8,5,8] = [16,10,16] 。总共 2 个成功组合。 +所以返回 [2,0,2] 。 +``` + + **提示:** + +* `n == spells.length` +* `m == potions.length` +* `1 <= n, m <= 10^5` +* `1 <= spells[i], potions[i] <= 10^5` +* `1 <= success <= 10^10` + +--- + +二分查找: + +先将 `potions` 数组进行排序,再通过二分搜索找到大于等于 `success` 的最小下标,即 `target = success` 的**左端点**。 + +```Java +class Solution { + public int[] successfulPairs(int[] spells, int[] potions, long success) { + int n = spells.length, m = potions.length; + int[] ans = new int[n]; + Arrays.sort(potions); + for(int i = 0; i < n; i++){ + ans[i] = dichotomy(potions, spells[i], success); + } + return ans; + } + + public int dichotomy(int[] array, int times, long target){ + int m = array.length, left = 0, right = m - 1; + while(left <= right){ + int mid = (left + right) / 2; + long temp = (long)array[mid] * (long)times; + if(temp >= target){ + right = mid - 1; + } else { + left = mid + 1; + } + } + return m - left; + } +} +``` \ No newline at end of file diff --git "a/Dichotomy/2517. \347\244\274\347\233\222\347\232\204\346\234\200\345\244\247\347\224\234\350\234\234\345\272\246.md" "b/Dichotomy/2517. \347\244\274\347\233\222\347\232\204\346\234\200\345\244\247\347\224\234\350\234\234\345\272\246.md" new file mode 100644 index 0000000..2142c88 --- /dev/null +++ "b/Dichotomy/2517. \347\244\274\347\233\222\347\232\204\346\234\200\345\244\247\347\224\234\350\234\234\345\272\246.md" @@ -0,0 +1,81 @@ +#### 2517. 礼盒的最大甜蜜度 + +难度:中等 + +--- + +给你一个正整数数组 `price` ,其中 `price[i]` 表示第 `i` 类糖果的价格,另给你一个正整数 `k` 。 + +商店组合 `k` 类 **不同** 糖果打包成礼盒出售。礼盒的 **甜蜜度** 是礼盒中任意两种糖果 **价格** 绝对差的最小值。 + +返回礼盒的 **最大** 甜蜜度_。_ + + **示例 1:** + +``` +输入:price = [13,5,1,8,21,2], k = 3 +输出:8 +解释:选出价格分别为 [13,5,21] 的三类糖果。 +礼盒的甜蜜度为 min(|13 - 5|, |13 - 21|, |5 - 21|) = min(8, 8, 16) = 8 。 +可以证明能够取得的最大甜蜜度就是 8 。 +``` + + **示例 2:** + +``` +输入:price = [1,3,1], k = 2 +输出:2 +解释:选出价格分别为 [1,3] 的两类糖果。 +礼盒的甜蜜度为 min(|1 - 3|) = min(2) = 2 。 +可以证明能够取得的最大甜蜜度就是 2 。 +``` + + **示例 3:** + +``` +输入:price = [7,7,7,7], k = 2 +输出:0 +解释:从现有的糖果中任选两类糖果,甜蜜度都会是 0 。 +``` + + **提示:** + +* `1 <= price.length <= 10^5` +* `1 <= price[i] <= 10^9` +* `2 <= k <= price.length` + +--- + +贪心 + 二分查找: + +将 `price` 数组排序,再以 `price[0]` 作为左端点,`price[n - 1] - price[0]` 作为右端点,不断寻找大于等于当前价格差的数组个数,若个数小于 `k`,意味着需要减少价格差,否则增加价格差。 + +```Java +class Solution { + public int maximumTastiness(int[] price, int k) { + int n = price.length; + Arrays.sort(price); + int left = 0, right = price[n - 1] - price[0]; + while(left <= right){ + int mid = (left + right) / 2; + if(exist(price, k, mid)){ + left = mid + 1; + } else { + right = mid - 1; + } + } + return left - 1; + } + + public boolean exist(int[] price, int k, int target){ + int count = 1, pre = price[0]; + for(int i = 1; i < price.length; i++){ + if(price[i] - pre >= target){ + count ++; + pre = price[i]; + } + } + return count >= k; + } +} +``` \ No newline at end of file diff --git "a/Dichotomy/2594. \344\277\256\350\275\246\347\232\204\346\234\200\345\260\221\346\227\266\351\227\264.md" "b/Dichotomy/2594. \344\277\256\350\275\246\347\232\204\346\234\200\345\260\221\346\227\266\351\227\264.md" new file mode 100644 index 0000000..eb7b197 --- /dev/null +++ "b/Dichotomy/2594. \344\277\256\350\275\246\347\232\204\346\234\200\345\260\221\346\227\266\351\227\264.md" @@ -0,0 +1,75 @@ +#### 2594. 修车的最少时间 + +难度:中等 + +--- + +给你一个整数数组 `ranks` ,表示一些机械工的 **能力值**  。`ranksi` 是第 `i` 位机械工的能力值。能力值为 `r` 的机械工可以在 `r * n^2` 分钟内修好 `n` 辆车。 + +同时给你一个整数 `cars` ,表示总共需要修理的汽车数目。 + +请你返回修理所有汽车  **最少**  需要多少时间。 + + **注意:** 所有机械工可以同时修理汽车。 + + **示例 1:** + +``` +输入:ranks = [4,2,3,1], cars = 10 +输出:16 +解释: +- 第一位机械工修 2 辆车,需要 4 * 2 * 2 = 16 分钟。 +- 第二位机械工修 2 辆车,需要 2 * 2 * 2 = 8 分钟。 +- 第三位机械工修 2 辆车,需要 3 * 2 * 2 = 12 分钟。 +- 第四位机械工修 4 辆车,需要 1 * 4 * 4 = 16 分钟。 +16 分钟是修理完所有车需要的最少时间。 +``` + + **示例 2:** + +``` +输入:ranks = [5,1,8], cars = 6 +输出:16 +解释: +- 第一位机械工修 1 辆车,需要 5 * 1 * 1 = 5 分钟。 +- 第二位机械工修 4 辆车,需要 1 * 4 * 4 = 16 分钟。 +- 第三位机械工修 1 辆车,需要 8 * 1 * 1 = 8 分钟。 +16 分钟时修理完所有车需要的最少时间。 +``` + + **提示:** + +* `1 <= ranks.length <= 10^5` +* `1 <= ranks[i] <= 100` +* `1 <= cars <= 10^6` + +--- + +二分查找: + +按时间维度进行二分查找,上界可以取正无穷,也可以取任意一个工人修完所有车辆所需要的时间;下界取 `1`,最少需要的时间。如果 `t` 时间内完成修车任务,那么一定是所有工人一起工作。 + +```Java +class Solution { + public long repairCars(int[] ranks, int cars) { + long left = 1, right = 1l * ranks[0] * cars * cars; + while(left <= right){ + long mid = (right - left) / 2 + left; + if(check(ranks, cars, mid)){ + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + private boolean check(int[] ranks, int cars, long time){ + long count = 0; + for(int rank: ranks){ + count += (long) Math.sqrt(time / rank); + } + return count >= cars; + } +} +``` \ No newline at end of file diff --git "a/Dichotomy/274. H \346\214\207\346\225\260.md" "b/Dichotomy/274. H \346\214\207\346\225\260.md" new file mode 100644 index 0000000..7d513e0 --- /dev/null +++ "b/Dichotomy/274. H \346\214\207\346\225\260.md" @@ -0,0 +1,61 @@ +#### 274. H 指数 + +难度:中等 + +--- + +给你一个整数数组 `citations` ,其中 `citations[i]` 表示研究者的第 `i` 篇论文被引用的次数。计算并返回该研究者的 **`h` 指数** 。 + +根据维基百科上 [h 指数的定义](https://baike.baidu.com/item/h-index/3991452?fr=aladdin):`h` 代表“高引用次数” ,一名科研人员的 `h` **指数** 是指他(她)至少发表了 `h` 篇论文,并且  **至少**  有 `h` 篇论文被引用次数大于等于 `h` 。如果 `h` 有多种可能的值, **`h` 指数** 是其中最大的那个。 + + **示例 1:** + +``` +输入:citations = [3,0,6,1,5] +输出:3 +解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。 +  由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。 +``` + + **示例 2:** + +``` +输入:citations = [1,3,1] +输出:1 +``` + + **提示:** + +* `n == citations.length` +* `1 <= n <= 5000` +* `0 <= citations[i] <= 1000` + +--- + +二分: + +显然从 `0` 遍历至 `n` 逐个检查是没问题的,因此二分也适用。 + +此处需要注意的是 `mid` 值的选取,因为 `left = mid`,当 `left + 1 = right` 时会死循环,所以 `mid = (left + right + 1) / 2`。另外每次检查满足条件的话,则最后结果是大于等于该 `h` 值,否则小于。 + +```Java +class Solution { + public int hIndex(int[] citations) { + int n = citations.length, left = 0, right = n; + while(left < right){ + int mid = (right + left + 1) / 2; + if(check(citations, mid)) left = mid; + else right = mid - 1; + } + return left; + } + + private boolean check(int[] citations, int target){ + int cnt = 0; + for(int citation: citations){ + if(citation >= target) cnt++; + } + return cnt >= target; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" "b/Dynamic Programming/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" new file mode 100644 index 0000000..ce69758 --- /dev/null +++ "b/Dynamic Programming/1143. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.md" @@ -0,0 +1,66 @@ +#### 1143. 最长公共子序列 + +难度:中等 + +--- + +给定两个字符串 `text1` 和 `text2`,返回这两个字符串的最长 **公共子序列** 的长度。如果不存在 **公共子序列** ,返回 `0` 。 + +一个字符串的  **子序列**  是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 + +* 例如,`"ace"` 是 `"abcde"` 的子序列,但 `"aec"` 不是 `"abcde"` 的子序列。 + +两个字符串的 **公共子序列** 是这两个字符串所共同拥有的子序列。 + + **示例 1:** + +``` +输入:text1 = "abcde", text2 = "ace" +输出:3 +解释:最长公共子序列是 "ace" ,它的长度为 3 。 +``` + + **示例 2:** + +``` +输入:text1 = "abc", text2 = "abc" +输出:3 +解释:最长公共子序列是 "abc" ,它的长度为 3 。 +``` + + **示例 3:** + +``` +输入:text1 = "abc", text2 = "def" +输出:0 +解释:两个字符串没有公共子序列,返回 0 。 +``` + + **提示:** + +* `1 <= text1.length, text2.length <= 1000` +* `text1` 和 `text2` 仅由小写英文字符组成。 + +--- + +动态规划: + +定义 `dp[i][j]` 为 `text1[0:i]` 和 `text2[0:j]` 的最长公共子序列的长度。 + +状态转移方程为:当 `text1[i] == text2[j]` 时,`dp[i][j] = dp[i - 1][j - 1] + 1`;当 `text1[i] != text2[j]` 时,`dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])`。 + +```Java +class Solution { + public int longestCommonSubsequence(String text1, String text2) { + int m = text1.length(), n = text2.length(); + int[][] dp = new int[m + 1][n + 1]; + for(int i = 1; i <= m; i++){ + for(int j = 1; j <= n; j++){ + if(text1.charAt(i - 1) == text2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1] + 1; + else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + } + return dp[m][n]; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" "b/Dynamic Programming/121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" new file mode 100644 index 0000000..2f9a130 --- /dev/null +++ "b/Dynamic Programming/121. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.md" @@ -0,0 +1,53 @@ +#### 121. 买卖股票的最佳时机 + +难度:简单 + +--- + +给定一个数组 `prices` ,它的第 `i` 个元素 `prices[i]` 表示一支给定股票第 `i` 天的价格。 + +你只能选择 **某一天** 买入这只股票,并选择在 **未来的某一个不同的日子** 卖出该股票。设计一个算法来计算你所能获取的最大利润。 + +返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 `0` 。 + + **示例 1:** + +``` +输入:[7,1,5,3,6,4] +输出:5 +解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 + 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 +``` + + **示例 2:** + +``` +输入:prices = [7,6,4,3,1] +输出:0 +解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 +``` + + **提示:** + +* `1 <= prices.length <= 10^5` +* `0 <= prices[i] <= 10^4` + +--- + +动态规划: + +一次遍历。遍历的过程中更新最小值,`dp[i]` 表示第 `i` 天卖出股票的利润,值为当前价格减去最小值,最后取最小值的最大值即可。 + +```Java +class Solution { + public int maxProfit(int[] prices) { + int n = prices.length, minn = prices[0]; + int[] dp = new int[n]; + for(int i = 0; i < n; i++){ + minn = Math.min(minn, prices[i]); + dp[i] = prices[i] - minn; + } + return Arrays.stream(dp).max().getAsInt(); + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" "b/Dynamic Programming/122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" new file mode 100644 index 0000000..743e737 --- /dev/null +++ "b/Dynamic Programming/122. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272 II.md" @@ -0,0 +1,76 @@ +#### 122. 买卖股票的最佳时机 II + +难度:中等 + +--- + +给你一个整数数组 `prices` ,其中 `prices[i]` 表示某支股票第 `i` 天的价格。 + +在每一天,你可以决定是否购买和/或出售股票。你在任何时候  **最多**  只能持有 **一股** 股票。你也可以先购买,然后在 **同一天** 出售。 + +返回 _你能获得的 **最大** 利润_ 。 + + **示例 1:** + +``` +输入:prices = [7,1,5,3,6,4] +输出:7 +解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 +  随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 + 总利润为 4 + 3 = 7 。 +``` + + **示例 2:** + +``` +输入:prices = [1,2,3,4,5] +输出:4 +解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 +  总利润为 4 。 +``` + + **示例 3:** + +``` +输入:prices = [7,6,4,3,1] +输出:0 +解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。 +``` + + **提示:** + +* `1 <= prices.length <= 3 * 10^4` +* `0 <= prices[i] <= 10^4` + +--- + +贪心/动态规划: + +一次遍历。`dp[i]` 表示从第一天到第 `i + 1` 天内的最大利润(并不意味着第 `i + 1` 天卖出股票),其值要么是上一个值,要么是上一个值加上**当前价格与昨天价格的差**(这样做的原因是题目中允许当天购买和出售),最后返回数组最后一位值即可。 + +```Java +class Solution { + public int maxProfit(int[] prices) { + int n = prices.length; + int[] dp = new int[n]; + for(int i = 1; i < n; i++){ + dp[i] = Math.max(dp[i - 1], dp[i - 1] + prices[i] - prices[i - 1]); // 状态转移方程 + } + return dp[n - 1]; + } +``` + +**贪心**:由于题目允许当天购买和出售,因此只要前后两天收益差值大于零就可以计入股票收益中。 + +```go +func maxProfit(prices []int) int { + ans := 0 + for i := 1; i < len(prices); i++ { + ans += max(0, prices[i] - prices[i - 1]) + } + return ans +} +``` + + + diff --git "a/Dynamic Programming/1626. \346\227\240\347\237\233\347\233\276\347\232\204\346\234\200\344\275\263\347\220\203\351\230\237.md" "b/Dynamic Programming/1626. \346\227\240\347\237\233\347\233\276\347\232\204\346\234\200\344\275\263\347\220\203\351\230\237.md" new file mode 100644 index 0000000..d9cf933 --- /dev/null +++ "b/Dynamic Programming/1626. \346\227\240\347\237\233\347\233\276\347\232\204\346\234\200\344\275\263\347\220\203\351\230\237.md" @@ -0,0 +1,84 @@ +#### 1626. 无矛盾的最佳球队 + +难度:中等 + +--- + +假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 **总和** 。 + +然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 **没有矛盾** 的球队。如果一名年龄较小球员的分数 **严格大于** 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。 + +给你两个列表 `scores` 和 `ages`,其中每组 `scores[i]` 和 `ages[i]` 表示第 `i` 名球员的分数和年龄。请你返回 **所有可能的无矛盾球队中得分最高那支的分数** 。 + + **示例 1:** + +``` +输入:scores = [1,3,5,10,15], ages = [1,2,3,4,5] +输出:34 +解释:你可以选中所有球员。 +``` + + **示例 2:** + +``` +输入:scores = [4,5,6,5], ages = [2,1,2,1] +输出:16 +解释:最佳的选择是后 3 名球员。注意,你可以选中多个同龄球员。 +``` + + **示例 3:** + +``` +输入:scores = [1,2,3,5], ages = [8,9,10,1] +输出:6 +解释:最佳的选择是前 3 名球员。 +``` + + **提示:** + +* `1 <= scores.length, ages.length <= 1000` +* `scores.length == ages.length` +* `1 <= scores[i] <= 10^6` +* `1 <= ages[i] <= 1000` + +--- + +排序 + 动态规划: + +`people[i][0], people[i][1]` 分别表示下标为 `i` 的人的分数和年龄。 + +将 `people` 数组按照**分数降序**进行排序,分数相同时,则按照**年龄降序**进行排序。 + +动态规划部分,`dp[i]` 表示最后一名球员的下标为 `i` 时能获得的最大分数,求解 `dp[i]` 需要向前遍历查找年龄满足条件(年龄大于等于当前下标为 `i` )的最大值情况,遍历数组取最大即可。即[此处](https://github.com/CompetitiveLin/Leetcode/blob/master/Dynamic%20Programming/README.md)的**第一种**情况。 + +这里需要注意的是: + +1. 不能将年龄升序排序。因为是从后向前遍历查找,当分数相同时,如果按年龄升序排序,不满足条件的情况反而出现在数组前面(后遍历到)。 +2. “如果一名年龄较小球员的分数 **严格大于** 一名年龄较大的球员,则存在矛盾。”其反义是:年龄较大球员的分数大于等于另一位球员 **或者** 两球员年龄相同,不存在矛盾。 + +```Java +class Solution { + public int bestTeamScore(int[] scores, int[] ages) { + int n = scores.length; + int[][] people = new int[n][2]; + for(int i = 0; i < n; i++){ + people[i][0] = scores[i]; + people[i][1] = ages[i]; + } + Arrays.sort(people, (a, b) -> (a[0] != b[0] ? b[0] - a[0] : b[1] - a[1])); + int[] dp = new int[n]; + dp[0] = people[0][0]; + int ans = dp[0]; + for(int i = 1; i < n; i++){ + for(int j = i - 1; j >= 0; j--){ + if(people[j][1] >= people[i][1]){ + dp[i] = Math.max(dp[i], dp[j]); + } + } + dp[i] += people[i][0]; + ans = Math.max(ans, dp[i]); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/198. \346\211\223\345\256\266\345\212\253\350\210\215.md" "b/Dynamic Programming/198. \346\211\223\345\256\266\345\212\253\350\210\215.md" new file mode 100644 index 0000000..c7c29f4 --- /dev/null +++ "b/Dynamic Programming/198. \346\211\223\345\256\266\345\212\253\350\210\215.md" @@ -0,0 +1,60 @@ +#### 198. 打家劫舍 + +难度:中等 + +--- + +你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统, **如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警** 。 + +给定一个代表每个房屋存放金额的非负整数数组,计算你 **不触动警报装置的情况下** ,一夜之内能够偷窃到的最高金额。 + + **示例 1:** + +``` +输入:[1,2,3,1] +输出:4 +解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 +  偷窃到的最高金额 = 1 + 3 = 4 。 +``` + + **示例 2:** + +``` +输入:[2,7,9,3,1] +输出:12 +解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 +  偷窃到的最高金额 = 2 + 9 + 1 = 12 。 +``` + + **提示:** + +* `1 <= nums.length <= 100` +* `0 <= nums[i] <= 400` + +--- + +动态规划: + +子问题:偷**前 `k` 个房子**的最大金额。 + +状态转移方程: +$$ +dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) +$$ + + +```Java +class Solution { + public int rob(int[] nums) { + int n = nums.length; + if(n == 1) return nums[0]; + int[] dp = new int[n]; + dp[0] = nums[0]; + dp[1] = Math.max(nums[0], nums[1]); + for(int i = 2; i < n; i++){ + dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); + } + return dp[n - 1]; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" "b/Dynamic Programming/213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" new file mode 100644 index 0000000..e29d965 --- /dev/null +++ "b/Dynamic Programming/213. \346\211\223\345\256\266\345\212\253\350\210\215 II.md" @@ -0,0 +1,66 @@ +#### 213. 打家劫舍 II + +难度:中等 + +--- + +你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 **围成一圈** ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统, **如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警** 。 + +给定一个代表每个房屋存放金额的非负整数数组,计算你 **在不触动警报装置的情况下** ,今晚能够偷窃到的最高金额。 + + **示例 1:** + +``` +输入:nums = [2,3,2] +输出:3 +解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 +``` + + **示例 2:** + +``` +输入:nums = [1,2,3,1] +输出:4 +解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 +  偷窃到的最高金额 = 1 + 3 = 4 。 +``` + + **示例 3:** + +``` +输入:nums = [1,2,3] +输出:3 +``` + + **提示:** + +* `1 <= nums.length <= 100` +* `0 <= nums[i] <= 1000` + +--- + +动态规划: + +与打家劫舍相比,考虑以下两种情况: + +1. 偷 `nums[0]`,那么 `nums[1]` 和 `nums[n - 1]` 不能偷,问题变成了 `nums[2]` 到 `nums[n - 2]` 的打家劫舍 +2. 不偷 `nums[0]`,那么问题变成了 `nums[1]` 到 `nums[n - 1]` 的打家劫舍 + +```Java +class Solution { + public int rob(int[] nums) { + int n = nums.length; + return Math.max(nums[0] + subrob(nums, 2, n - 1), subrob(nums, 1, n)); + } + + private int subrob(int[] nums, int left, int right){ + int f0 = 0, f1 = 0; + for(int i = left; i < right; i++){ + int newF = Math.max(f1, f0 + nums[i]); + f0 = f1; + f1 = newF; + } + return f1; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/264. \344\270\221\346\225\260 II.md" "b/Dynamic Programming/264. \344\270\221\346\225\260 II.md" new file mode 100644 index 0000000..95d56f0 --- /dev/null +++ "b/Dynamic Programming/264. \344\270\221\346\225\260 II.md" @@ -0,0 +1,81 @@ +#### 264. 丑数 II + +难度:中等 + +--- + +给你一个整数 `n` ,请你找出并返回第 `n` 个 **丑数** 。 + + **丑数** 就是只包含质因数 `2`、`3` 和/或 `5` 的正整数。 + + **示例 1:** + +``` +输入:n = 10 +输出:12 +解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。 +``` + + **示例 2:** + +``` +输入:n = 1 +输出:1 +解释:1 通常被视为丑数。 +``` + + **提示:** + +* `1 <= n <= 1690` + +--- + +最小堆: + +已知后面的丑数肯定是前面的丑数乘以2/3/5得到的。那么构造一个存放丑数的最小堆,每次弹出最小值,并且放入该值与2/3/5相乘的结果,并需要**去重**。 + +```java +class Solution { +    public int nthUglyNumber(int n) { +        PriorityQueue pq = new PriorityQueue<>(); +        pq.offer(1l); +        long ans = 1l; +        while(n-- != 0){ +            ans = pq.poll(); +            if(!pq.contains(ans * 2l)) pq.offer(ans * 2l); +            if(!pq.contains(ans * 3l)) pq.offer(ans * 3l); +            if(!pq.contains(ans * 5l)) pq.offer(ans * 5l); +        } +        return (int)ans; +    } +} +``` + + + +动态规划: + +定义三个指针 *p*2, *p*3, *p*5,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。 + +```go +func nthUglyNumber(n int) int { + dp := make([]int, n + 1) + dp[1] = 1 + x2, x3, x5 := 1, 1, 1 + for i := 2; i <= n; i++ { + s2, s3, s5 := dp[x2] * 2, dp[x3] * 3, dp[x5] * 5 + dp[i] = min(min(s2, s3), s5) + if dp[i] == s2 { + x2++ + } + if dp[i] == s3 { // 不能用 else if,而是用 if。因为会有重复计算 + x3++ + } + if dp[i] == s5 { + x5++ + } + } + return dp[n] +} +``` + diff --git "a/Dynamic Programming/2786. \350\256\277\351\227\256\346\225\260\347\273\204\344\270\255\347\232\204\344\275\215\347\275\256\344\275\277\345\210\206\346\225\260\346\234\200\345\244\247.md" "b/Dynamic Programming/2786. \350\256\277\351\227\256\346\225\260\347\273\204\344\270\255\347\232\204\344\275\215\347\275\256\344\275\277\345\210\206\346\225\260\346\234\200\345\244\247.md" new file mode 100644 index 0000000..dc4ff58 --- /dev/null +++ "b/Dynamic Programming/2786. \350\256\277\351\227\256\346\225\260\347\273\204\344\270\255\347\232\204\344\275\215\347\275\256\344\275\277\345\210\206\346\225\260\346\234\200\345\244\247.md" @@ -0,0 +1,61 @@ +#### 2786. 访问数组中的位置使分数最大 + +难度:中等 + +--- + +给你一个下标从 **0**  开始的整数数组 `nums` 和一个正整数 `x` 。 + +你 **一开始**  在数组的位置 `0` 处,你可以按照下述规则访问数组中的其他位置: + +* 如果你当前在位置 `i` ,那么你可以移动到满足 `i < j` 的  **任意**  位置 `j` 。 +* 对于你访问的位置 `i` ,你可以获得分数 `nums[i]` 。 +* 如果你从位置 `i` 移动到位置 `j` 且 `nums[i]` 和 `nums[j]` 的 **奇偶性**  不同,那么你将失去分数 `x` 。 + +请你返回你能得到的 **最大**  得分之和。 + + **注意**  ,你一开始的分数为 `nums[0]` 。 + + **示例 1:** + +``` +输入:nums = [2,3,6,1,9,2], x = 5 +输出:13 +解释:我们可以按顺序访问数组中的位置:0 -> 2 -> 3 -> 4 。 +对应位置的值为 2 ,6 ,1 和 9 。因为 6 和 1 的奇偶性不同,所以下标从 2 -> 3 让你失去 x = 5 分。 +总得分为:2 + 6 + 1 + 9 - 5 = 13 。 +``` + + **示例 2:** + +``` +输入:nums = [2,4,6,8], x = 3 +输出:20 +解释:数组中的所有元素奇偶性都一样,所以我们可以将每个元素都访问一次,而且不会失去任何分数。 +总得分为:2 + 4 + 6 + 8 = 20 。 +``` + + **提示:** + +* `2 <= nums.length <= 10^5` +* `1 <= nums[i], x <= 10^6` + +--- + +动态规划: + +从左往右遍历的过程中,只取决于最近偶数时和最近奇数时得分的最大值。然后再根据当前值和两个最大值进行比较,取更大者即可。 + +```Go +func maxScore(nums []int, x int) int64 { + res := int64(nums[0]) + dp := []int64{math.MinInt32, math.MinInt32} + dp[nums[0] % 2] = int64(nums[0]) + for i := 1; i < len(nums); i++ { + maxn := max(dp[nums[i] % 2], dp[(nums[i] + 1) % 2] - int64(x)) + int64(nums[i]) + res = max(res, maxn) + dp[nums[i] % 2] = max(dp[nums[i] % 2], maxn) + } + return res +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" "b/Dynamic Programming/279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" new file mode 100644 index 0000000..51c97cc --- /dev/null +++ "b/Dynamic Programming/279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" @@ -0,0 +1,55 @@ +#### 279. 完全平方数 + +难度:中等 + +--- + +给你一个整数 `n` ,返回 _和为 `n` 的完全平方数的最少数量_ 。 + + **完全平方数** 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,`1`、`4`、`9` 和 `16` 都是完全平方数,而 `3` 和 `11` 不是。 + + **示例 1:** + +``` +输入:n = 12 +输出:3 +解释:12 = 4 + 4 + 4 +``` + + **示例 2:** + +``` +输入:n = 13 +输出:2 +解释:13 = 4 + 9 +``` + +  + + **提示:** + +* `1 <= n <= 10^4` + +--- + +动态规划: + +`dp[i]` 指和为 `i` 的完全平方数的最少数量。状态转移方程为 $dp[i] = \min_{j = 1, .., \sqrt i}dp[i - j * j] + 1$。最少数量肯定是当前值减去一个完全平方数得到的,但是具体哪个完全平方数,需要遍历得到。 + +与 [322. 零钱兑换](https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/) 类似! + +```Java +class Solution { + public int numSquares(int n) { + int[] dp = new int[n + 1]; + for(int i = 1; i <= n; i++){ + int minn = Integer.MAX_VALUE; + for(int j = 1; j * j <= i; j++){ + minn = Math.min(minn, dp[i - j * j]); + } + dp[i] += minn + 1; + } + return dp[n]; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" "b/Dynamic Programming/300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" new file mode 100644 index 0000000..5b6dd1c --- /dev/null +++ "b/Dynamic Programming/300. \346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.md" @@ -0,0 +1,117 @@ +#### 300. 最长递增子序列 + +难度:中等 + +--- + +给你一个整数数组 `nums` ,找到其中最长严格递增子序列的长度。 + + **子序列**  是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,`[3,6,2,7]` 是数组 `[0,3,1,6,2,2,7]` 的子序列。 + +  + + **示例 1:** + +``` +输入:nums = [10,9,2,5,3,7,101,18] +输出:4 +解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。 +``` + + **示例 2:** + +``` +输入:nums = [0,1,0,3,2,3] +输出:4 +``` + + **示例 3:** + +``` +输入:nums = [7,7,7,7,7,7,7] +输出:1 +``` + + **提示:** + +* `1 <= nums.length <= 2500` +* `-10^4 <= nums[i] <= 10^4` + +**进阶:** + +* 你能将算法的时间复杂度降低到 `O(n log(n))` 吗? + +--- + +动态规划: + +与 [1626. 无矛盾的最佳球队 ](https://leetcode.cn/problems/best-team-with-no-conflicts/)类似。 + +```Java +class Solution { + public int lengthOfLIS(int[] nums) { + int n = nums.length; + int[] dp = new int[n]; + dp[0] = 1; + int ans = dp[0]; + for(int i = 1; i < n; i++){ + for(int j = i - 1; j >= 0; j--){ + if(nums[j] < nums[i]){ + dp[i] = Math.max(dp[i], dp[j]); + } + } + dp[i] += 1; + ans = Math.max(ans, dp[i]); + } + return ans; + } +} +``` + + + +或者初始化 `dp` 数组都为 `1`,然后仍然是遍历比较大小。 + +```java +class Solution { + public int lengthOfLIS(int[] nums) { + int n = nums.length; + int[] dp = new int[n]; + for(int i = 0; i < n; i++) dp[i] = 1; + int res = 1; + for(int i = 1; i < nums.length; i++){ + for(int j = 0; j < i; j++){ + if(nums[i] > nums[j]){ + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + res = Math.max(res, dp[i]); + } + return res; + } +} +``` + + + +Go 语言: + +```go +func lengthOfLIS(nums []int) int { + dp := make([]int, len(nums)) + dp[0] = 1 + res := dp[0] + for i := 1; i < len(nums); i++ { + maxn := 0 + for j := 0; j < i; j++ { + if nums[i] > nums[j] { + maxn = max(maxn, dp[j]) + } + } + dp[i] = maxn + 1 + res = max(res, dp[i]) + } + return res +} +``` + diff --git "a/Dynamic Programming/309. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" "b/Dynamic Programming/309. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" new file mode 100644 index 0000000..d5315fc --- /dev/null +++ "b/Dynamic Programming/309. \344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.md" @@ -0,0 +1,87 @@ +#### 309. 买卖股票的最佳时机含冷冻期 + +难度:中等 + +--- + +给定一个整数数组`prices`,其中第  `prices[i]` 表示第 `_i_` 天的股票价格 。​ + +设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): + +* 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 + + **注意:** 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + + **示例 1:** + +``` +输入: prices = [1,2,3,0,2] +输出: 3 +解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出] +``` + + **示例 2:** + +``` +输入: prices = [1] +输出: 0 +``` + + **提示:** + +* `1 <= prices.length <= 5000` +* `0 <= prices[i] <= 1000` + +--- + +多维动态规划: + +一共有三种状态: + +- `dp[i][0]`: 不持股且当天不卖出,若要达到这种状态,则有两种情况,前一天的股票**不卖出或者卖出**,即 `dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1])`。 +- `dp[i][1]`: 不持股且当天卖出,若要达到这种状态,只有一种情况,就是前一天股票持有的情况下的最大收益加上今天股票的价格,即 `dp[i][1] = dp[i - 1][2] + prices[i]`。 +- `dp[i][2]`: 持股,若要达到这种状态,有可能是**前一天持股的最大收益或者今天买入**,即 `dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0] - prices[i])`。 + +```Java +// 思路: +// 考虑有多少种状态,每种状态有哪些选择,或者是做了哪些选择后得到哪种状态。 +// 注意:到底是先选择了才有状态,还是先由状态才能选择。这里是先选择了,才有状态 + +// 状态类型有2种:天数和是否持有。 +// 天数:一共为1-n天 +// 是否持有:分为持有状态、没持有状态1、没持有状态2。 +// 持有状态:选择 无处理 和 买入 都有可能达到该状态 +// 没持有状态1:选择 无处理 后达到该状态。 +// 没持有状态2:选择 卖出 后达到该状态。注意,卖出后进入一天的冻结期。 +// 注意:这里为什么要分两种没持有状态,这是为了便于后续状态转移,如果不区分这两种状态,状态转移没法确定当天是否可以进行买入操作。 + +// dp表示的含义: +// dp[i][2] : 第i天为没持有状态2时,此时的最大利润 +// dp[i][1] : 第i天为没持有状态1时,此时的最大利润 +// dp[i][0] : 第i天为持有状态时,此时的最大利润 +// 状态转移方程: +// dp[i][0]: 第i天为持有状态时,此时的最大利润 +// 无处理后达到该状态: dp[i][0] = dp[i-1][0] // 第i天没有处理就持有股票,证明上一天也持有 +// 买入后达到该状态: dp[i][0] = dp[i-1][1]-prices[n] // 第i天能买入股票,证明上一天没持有股票,且没进行卖出操作 +// 所以dp[i][0] = max(dp[i-1][0], dp[i-1][1]-prices[n]); // 这里思考个问题,两种情况都能到达这个状态的话,那如何选择?为什么是取他们的max? +// dp[i][1]: 第i天为没持有状态1时,此时的最大利润 +// 无处理后达到该状态: dp[i][1] = max(dp[i-1][1], dp[i-1][2]) // 有两种到达该状态的情况,取最大那个 +// dp[i][2]: 第i天为没持有状态2时,此时的最大利润 +// 卖出后达到该状态: dp[i][2] = dp[i-1][0]+prices[i] + +// 最后max(dp[n-1][1], dp[n-1][2])就是题目所需答案。即第n-1天没持有股票时的最大收益 +//======================================================================================= +class Solution { + public int maxProfit(int[] prices) { + int n = prices.length; + int[][] dp = new int[n][3]; + dp[0][2] = -1 * prices[0]; // dp[i][0]: 不持股且当天不卖出; dp[i][1]: 不持股且当天卖出; dp[i][2]: 持股 + for(int i = 1; i < n; i++){ + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); + dp[i][1] = dp[i - 1][2] + prices[i]; + dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0] - prices[i]); + } + return Math.max(dp[n - 1][0], dp[n - 1][1]); + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/3152. \347\211\271\346\256\212\346\225\260\347\273\204 II.md" "b/Dynamic Programming/3152. \347\211\271\346\256\212\346\225\260\347\273\204 II.md" new file mode 100644 index 0000000..09e7840 --- /dev/null +++ "b/Dynamic Programming/3152. \347\211\271\346\256\212\346\225\260\347\273\204 II.md" @@ -0,0 +1,66 @@ +#### 3152. 特殊数组 II + +难度:中等 + +--- + +如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 **特殊数组** 。 + +周洋哥有一个整数数组 `nums` 和一个二维整数矩阵 `queries`,对于 `queries[i] = [fromi, toi]`,请你帮助周洋哥检查子数组 `nums[fromi..toi]` 是不是一个 **特殊数组** 。 + +返回布尔数组 `answer`,如果 `nums[fromi..toi]` 是特殊数组,则 `answer[i]` 为 `true` ,否则,`answer[i]` 为 `false` 。 + + **示例 1:** + + **输入:** nums = \[3,4,1,2,6\], queries = \[\[0,4\]\] + + **输出:** \[false\] + + **解释:** + +子数组是 `[3,4,1,2,6]`。2 和 6 都是偶数。 + + **示例 2:** + + **输入:** nums = \[4,3,1,6\], queries = \[\[0,2\],\[2,3\]\] + + **输出:** \[false,true\] + + **解释:** + +1. 子数组是 `[4,3,1]`。3 和 1 都是奇数。因此这个查询的答案是 `false`。 +2. 子数组是 `[1,6]`。只有一对:`(1,6)`,且包含了奇偶性不同的数字。因此这个查询的答案是 `true`。 + + **提示:** + +* `1 <= nums.length <= 10^5` +* `1 <= nums[i] <= 10^5` +* `1 <= queries.length <= 10^5` +* `queries[i].length == 2` +* `0 <= queries[i][0] <= queries[i][1] <= nums.length - 1` + +--- + +动态规划: + +一维数组 `dp[i]` 表示下标 `i` 的数是特殊数组的第几个数。例如 `[3,4,1,2,6]` 所对应的 `dp[i]` 分别是 `[1, 2, 3, 4, 1]`,前四个数为一组特殊数组,最后一个数单独构成一组特殊数组。然后根据给定的区间和当前下标的值进行比较。 + +```Go +func isArraySpecial(nums []int, queries [][]int) []bool { + n := len(nums) + dp := make([]int, n) + dp[0] = 1 + for i := 1; i < n; i++ { + dp[i] = 1 + if ((nums[i] ^ nums[i - 1]) & 1 == 1) { + dp[i] = dp[i - 1] + 1 + } + } + res := []bool{} + for i := range queries { + gap := queries[i][1] - queries[i][0] + res = append(res, dp[queries[i][1]] >= gap + 1) + } + return res +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/322. \351\233\266\351\222\261\345\205\221\346\215\242.md" "b/Dynamic Programming/322. \351\233\266\351\222\261\345\205\221\346\215\242.md" new file mode 100644 index 0000000..550b7f5 --- /dev/null +++ "b/Dynamic Programming/322. \351\233\266\351\222\261\345\205\221\346\215\242.md" @@ -0,0 +1,102 @@ +#### 322. 零钱兑换 + +难度:中等 + +--- + +给你一个整数数组 `coins` ,表示不同面额的硬币;以及一个整数 `amount` ,表示总金额。 + +计算并返回可以凑成总金额所需的 **最少的硬币个数** 。如果没有任何一种硬币组合能组成总金额,返回 `-1` 。 + +你可以认为每种硬币的数量是无限的。 + + **示例 1:** + +``` +输入:coins = [1, 2, 5], amount = 11 +输出:3 +解释:11 = 5 + 5 + 1 +``` + + **示例 2:** + +``` +输入:coins = [2], amount = 3 +输出:-1 +``` + + **示例 3:** + +``` +输入:coins = [1], amount = 0 +输出:0 +``` + + **提示:** + +* `1 <= coins.length <= 12` +* `1 <= coins[i] <= 2^31 - 1` +* `0 <= amount <= 10^4` + +--- + +动态规划: + +每种硬币的数量是无限的,因此是**完全背包问题**。 + +`dp[i]` 指凑成总金额为 `i` 的最少硬币数量。状态转移方程为 $dp[i] = \min_{j = 0, .., n - 1}dp[i - coins[j]] + 1$。简而言之,取所有当前金额与 `coins` 数组之差最少硬币数量的最小值并加一。 + +假设 `coins = [1, 2, 5], amount = 11`,当 `i = 11` 时,取 ` coins` 数组中所有比 `11` 小的值,即 `dp[11 - 1], dp[11 - 2], dp[11 - 5]` 的最小值,最后加一即可。 + +与 [279. 完全平方数](https://leetcode.cn/problems/perfect-squares/) 类似,列举所有比当前值小的平方数,取其中 `dp` 数组的最小值。 + +```Java +class Solution { + public int coinChange(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + dp[0] = 0; + for(int i = 0; i < coins.length; i++){ + if(coins[i] <= amount) dp[coins[i]] = 1; + } + for(int i = 1; i <= amount; i++){ + int minn = Integer.MAX_VALUE; + for(int j = 0; j < coins.length; j++){ + if(i - coins[j] >= 0 && dp[i - coins[j]] != -1) minn = Math.min(minn, dp[i - coins[j]]); + } + if(minn != Integer.MAX_VALUE) dp[i] = minn + 1; + else dp[i] = -1; + } + return dp[amount]; + } +} +``` + + + +二维数组: `dp[i][j]` 表示前 `i` 个数组可以凑成总金额为 `j` 的最少硬币数量,并且初始化数组为最大值。 + +```Go +func coinChange(coins []int, amount int) int { + n := len(coins) + dp := make([][]int, n + 1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amount + 1) + for j := 1; j <= amount; j++ { + dp[i][j] = 1 << 30 + } + } + for i := 1; i <= n; i++ { + for j := 1; j <= amount; j++ { + dp[i][j] = dp[i - 1][j] + if j-coins[i-1] >= 0 { + dp[i][j] = min(min(dp[i][j], dp[i-1][j-coins[i - 1]]+1), dp[i][j-coins[i - 1]]+1) + } + } + } + if dp[n][amount] == 1<<30 { + return -1 + } + return dp[n][amount] +} +``` + diff --git "a/Dynamic Programming/416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" "b/Dynamic Programming/416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" new file mode 100644 index 0000000..67841be --- /dev/null +++ "b/Dynamic Programming/416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" @@ -0,0 +1,129 @@ +#### 416. 分割等和子集 + +难度:中等 + +--- + +给你一个 **只包含正整数** 的 **非空** 数组 `nums` 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 + + **示例 1:** + +``` +输入:nums = [1,5,11,5] +输出:true +解释:数组可以分割成 [1, 5, 5] 和 [11] 。 +``` + + **示例 2:** + +``` +输入:nums = [1,2,3,5] +输出:false +解释:数组不能分割成两个元素和相等的子集。 +``` + + **提示:** + +* `1 <= nums.length <= 200` +* `1 <= nums[i] <= 100` + +--- + +二维动态规划: + +`dp[i][j]` 是指从 `[0, i]` 范围内选取若干个数,其和为 `j` 的可行性。 + +初始状态 `dp[i][0] = true, dp[0][nums[0]] = true`。 + +状态转移方程:当 `nums[i] <= j` 时,`dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]`,即第一种情况不包含 `nums[i]`,第二种情况包含 `nums[i]`,两种情况用 **或** 相连接;当 `nums[i] > j` 时,`dp[i][j] = dp[i - 1][j]`,如果加了 `nums[i]` 之后大于 `j`,那只能不包含 `nums[i]`。 + +```Java +class Solution { + public boolean canPartition(int[] nums) { + int n = nums.length, sum = 0; + for(int num: nums){ + sum += num; + } + if(sum % 2 == 1) return false; + int target = sum / 2; + boolean[][] dp = new boolean[n][target + 1]; + dp[0][0] = true; + if(nums[0] <= target) dp[0][nums[0]] = true; + for(int i = 1; i < n; i++){ + for(int j = 0; j <= target; j++){ + dp[i][j] = dp[i - 1][j]; + if(nums[i] <= j){ + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; + } + } + } + return dp[n - 1][target]; + } +} +``` + + + +Go 语言: + +```go +func canPartition(nums []int) bool { + n, sum := len(nums), 0 + for _, value := range(nums) { + sum += value + } + if sum % 2 != 0 || n == 1 { + return false + } + target := sum / 2 + dp := make([][]bool, n) + for i := range dp { + dp[i] = make([]bool, target + 1) + dp[i][0] = true + } + if nums[0] <= target { + dp[0][nums[0]] = true + } + for i := 1; i < n; i++ { + for j := 1; j <= target; j++ { + if j > nums[i] { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]] + } else { + dp[i][j] = dp[i - 1][j] + } + } + } + return dp[n - 1][target] +} +``` + + + +一维数组: + +`dp[i]` 表示是否存在和为 `i` 的 num 组合。`target` 为数组和的二分之一,因为是求两个等和子集。 + +- 外层遍历 nums 每个 num; +- 内层遍历 target(由大到小)。 + +```go +func canPartition(nums []int) bool { + sum := 0 + for _, num := range nums { + sum += num + } + if sum % 2 == 1 { + return false + } + target := sum / 2 + dp := make([]bool, target + 1) + dp[0] = true + for _, num := range nums { + for j := target; j >= num; j-- { + dp[j] = dp[j] || dp[j - num] + } + } + return dp[target] +} +``` + diff --git "a/Dynamic Programming/494. \347\233\256\346\240\207\345\222\214.md" "b/Dynamic Programming/494. \347\233\256\346\240\207\345\222\214.md" new file mode 100644 index 0000000..2442acd --- /dev/null +++ "b/Dynamic Programming/494. \347\233\256\346\240\207\345\222\214.md" @@ -0,0 +1,86 @@ +#### 494. 目标和 + +难度:中等 + +--- + +给你一个非负整数数组 `nums` 和一个整数 `target` 。 + +向数组中的每个整数前添加 `'+'` 或 `'-'` ,然后串联起所有整数,可以构造一个 **表达式** : + +* 例如,`nums = [2, 1]` ,可以在 `2` 之前添加 `'+'` ,在 `1` 之前添加 `'-'` ,然后串联起来得到表达式 `"+2-1"` 。 + +返回可以通过上述方法构造的、运算结果等于 `target` 的不同 **表达式** 的数目。 + + **示例 1:** + +``` +输入:nums = [1,1,1,1,1], target = 3 +输出:5 +解释:一共有 5 种方法让最终目标和为 3 。 +-1 + 1 + 1 + 1 + 1 = 3 ++1 - 1 + 1 + 1 + 1 = 3 ++1 + 1 - 1 + 1 + 1 = 3 ++1 + 1 + 1 - 1 + 1 = 3 ++1 + 1 + 1 + 1 - 1 = 3 +``` + + **示例 2:** + +``` +输入:nums = [1], target = 1 +输出:1 +``` + + **提示:** + +* `1 <= nums.length <= 20` +* `0 <= nums[i] <= 1000` +* `0 <= sum(nums[i]) <= 1000` +* `-1000 <= target <= 1000` + +--- + +动态规划: + +假设所有元素和为 `sum`,那么所有元素都加上负号之后为 `-sum`, 所以元素和的变化值为 `[-sum, sum]`,因此将其范围平移至 `[0, 2 * sum + 1]`。并且定义二维数组 `dp[i][j]` 表示前 `i` 个数组的和为 `j` 的表达式数量。最后的结果为 `dp[len(sum)][sum + target]`。状态转移方程为: + +`dp[i][j] = dp[i - 1][j - nums[i - 1]] + dp[i - 1][j + nums[i - 1]]` + +```Go +func findTargetSumWays(nums []int, target int) int { + n := len(nums) + sum := 0 + for _, num := range nums { + sum += num + } + if abs(target) > sum { + return 0 + } + dp := make([][]int, n + 1) + m := 2 * sum + 1 + for i := range dp { + dp[i] = make([]int, m + 1) + } + dp[0][sum] = 1 + for i := 1; i <= n; i++ { + for j := 0; j <= m; j++ { + dp[i][j] = 0 + if j - nums[i - 1] >= 0{ + dp[i][j] += dp[i - 1][j - nums[i - 1]] + } + if j + nums[i - 1] <= m { + dp[i][j] += dp[i - 1][j + nums[i - 1]] + } + } + } + return dp[n][target + sum] +} + +func abs(a int) int { + if a < 0 { + return -a + } + return a +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" "b/Dynamic Programming/518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" new file mode 100644 index 0000000..92b0e61 --- /dev/null +++ "b/Dynamic Programming/518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" @@ -0,0 +1,91 @@ +#### 518. 零钱兑换 II + +难度:中等 + +--- + +给你一个整数数组 `coins` 表示不同面额的硬币,另给一个整数 `amount` 表示总金额。 + +请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 `0` 。 + +假设每一种面额的硬币有无限个。  + +题目数据保证结果符合 32 位带符号整数。 + + **示例 1:** + +``` +输入:amount = 5, coins = [1, 2, 5] +输出:4 +解释:有四种方式可以凑成总金额: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 +``` + + **示例 2:** + +``` +输入:amount = 3, coins = [2] +输出:0 +解释:只用面额 2 的硬币不能凑成总金额 3 。 +``` + + **示例 3:** + +``` +输入:amount = 10, coins = [10] +输出:1 +``` + + **提示:** + +* `1 <= coins.length <= 300` +* `1 <= coins[i] <= 5000` +* `coins` 中的所有值 **互不相同** +* `0 <= amount <= 5000` + +--- + +动态规划: + +1. 二维数组,`dp[i][j]` 表示前 `i` 个数组凑成金额为 `j` 的组合数量 + +```Go +func change(amount int, coins []int) int { + n := len(coins) + dp := make([][]int, n + 1) + for i := range dp { + dp[i] = make([]int, amount + 1) + } + dp[0][0] = 1 + for i := 1; i <= n; i++ { + for j := 0; j <= amount; j++ { + dp[i][j] = dp[i - 1][j] + if j >= coins[i - 1] { + dp[i][j] += dp[i][j - coins[i - 1]] + } + } + } + return dp[n][amount] +} +``` + + + +2. 一维数组,`dp[i]` 表示凑成金额为 `i` 的组合数量 + +```go +func change(amount int, coins []int) int { + dp := make([]int, amount + 1) + dp[0] = 1 + for _, coin := range coins { + for j := coin; j <= amount; j++ { + dp[j] += dp[j - coin] + } + } + return dp[amount] +} +``` + diff --git "a/Dynamic Programming/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" "b/Dynamic Programming/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" new file mode 100644 index 0000000..4131ccc --- /dev/null +++ "b/Dynamic Programming/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" @@ -0,0 +1,68 @@ +#### 53. 最大子数组和 + +难度:中等 + +--- + +给你一个整数数组 `nums` ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + + **子数组** 是数组中的一个连续部分。 + + **示例 1:** + +``` +输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +输出:6 +解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 +``` + + **示例 2:** + +``` +输入:nums = [1] +输出:1 +``` + + **示例 3:** + +``` +输入:nums = [5,4,-1,7,8] +输出:23 +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `-10^4 <= nums[i] <= 10^4` + + **进阶:** 如果你已经实现复杂度为 `O(n)` 的解法,尝试使用更为精妙的 **分治法** 求解。 + +--- + +动态规划: + +本次可直接在原数组上做修改。 + +状态定义:`dp[i]` 表示以 `nums[i]` 为结尾的连续子数组最大和。 + +状态转移方程为: +$$ +dp[i] = \left\{ \begin{array}{rcl} +&dp[i - 1] + nums[i],& dp[i - 1]>0 \\ +&nums[i], & dp[i - 1]\le0 +\end{array}\right. +$$ +类似多次求前缀和。当某一项的前缀和小于零时,下一项开始重新求前缀和。 + +```Java +class Solution { + public int maxSubArray(int[] nums) { + int res = nums[0]; + for(int i = 1; i < nums.length; i++){ + nums[i] += Math.max(nums[i - 1], 0); + res = Math.max(res, nums[i]); + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/62. \344\270\215\345\220\214\350\267\257\345\276\204.md" "b/Dynamic Programming/62. \344\270\215\345\220\214\350\267\257\345\276\204.md" new file mode 100644 index 0000000..29d8bc1 --- /dev/null +++ "b/Dynamic Programming/62. \344\270\215\345\220\214\350\267\257\345\276\204.md" @@ -0,0 +1,118 @@ +#### 62. 不同路径 + +难度:中等 + +--- + +一个机器人位于一个 `m x n` 网格的左上角 (起始点在下图中标记为 “Start” )。 + +机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 + +问总共有多少条不同的路径? + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) +``` +输入:m = 3, n = 7 +输出:28 +``` + + **示例 2:** + +``` +输入:m = 3, n = 2 +输出:3 +解释: +从左上角开始,总共有 3 条路径可以到达右下角。 +1. 向右 -> 向下 -> 向下 +2. 向下 -> 向下 -> 向右 +3. 向下 -> 向右 -> 向下 +``` + + **示例 3:** + +``` +输入:m = 7, n = 3 +输出:28 +``` + + **示例 4:** + +``` +输入:m = 3, n = 3 +输出:6 +``` + + **提示:** + +* `1 <= m, n <= 100` +* 题目数据保证答案小于等于 `2 * 10^9` + +--- + +方法一,简单动态规划: + +当前格的路径数为上方格的路径数加上左侧格的路径数,空间复杂度$O(m*n)$,时间复杂度$O(m*n)$ + +```java +class Solution { + public int uniquePaths(int m, int n) { + int[][] dp = new int[m][n]; + for(int i = 0; i < m; i++){ + dp[i][0] = 1; + } + for(int j = 0; j < n; j++){ + dp[0][j] = 1; + } + for(int i = 1; i < m; i++){ + for(int j = 1; j < n; j++){ + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; + } + +} +``` + + + +方法二,法一的优化方法,时间复杂度$O(m * n)$,空间复杂度$O(n)$。 + +```Java +class Solution { + public int uniquePaths(int m, int n) { + int[] dp = new int[n]; + for(int i = 0; i < n; i++){ + dp[i] = 1; + } + for(int i = 1; i < m; i++){ + for(int j = 1; j < n; j++){ + dp[j] += dp[j - 1]; + } + } + return dp[n - 1]; + } + +} +``` + + + +方法三,排列组合: + +从左上角移动到右下角一共需要移动 `m + n - 2` 次,其中 `m - 1` 次向下移动,`n - 1` 次向右移动。因此路径的总数是从 `m + n - 2` 次的移动中选择 `m - 1` 次的向下移动(或者 `n - 1` 的向右移动)。即 $C_{m+n-2}^{m - 1}$ (或 $C_{m+n-2}^{n - 1}$) $= \frac{(m+n-2)!}{(m-1)!(n-1)!}$。时间复杂度$O(m)$,空间复杂度$O(1)$。 + +```java +class Solution { + public int uniquePaths(int m, int n) { + long ans = 1; + for (int x = n, y = 1; y < m; ++x, ++y) { + ans = ans * x / y; + } + return (int) ans; + } +} +``` + diff --git "a/Dynamic Programming/91. \350\247\243\347\240\201\346\226\271\346\263\225.md" "b/Dynamic Programming/91. \350\247\243\347\240\201\346\226\271\346\263\225.md" new file mode 100644 index 0000000..ba2704c --- /dev/null +++ "b/Dynamic Programming/91. \350\247\243\347\240\201\346\226\271\346\263\225.md" @@ -0,0 +1,78 @@ +#### 91. 解码方法 + +难度:中等 + +--- + +一条包含字母 `A-Z` 的消息通过以下映射进行了 **编码** : + +``` +'A' -> "1" +'B' -> "2" +... +'Z' -> "26" +``` + +要 **解码** 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,`"11106"` 可以映射为: + +* `"AAJF"` ,将消息分组为 `(1 1 10 6)` +* `"KJF"` ,将消息分组为 `(11 10 6)` + +注意,消息不能分组为  `(1 11 06)` ,因为 `"06"` 不能映射为 `"F"` ,这是由于 `"6"` 和 `"06"` 在映射中并不等价。 + +给你一个只含数字的 **非空** 字符串 `s` ,请计算并返回 **解码** 方法的 **总数** 。 + +题目数据保证答案肯定是一个 **32 位** 的整数。 + + **示例 1:** + +``` +输入:s = "12" +输出:2 +解释:它可以解码为 "AB"(1 2)或者 "L"(12)。 +``` + + **示例 2:** + +``` +输入:s = "226" +输出:3 +解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 +``` + + **示例 3:** + +``` +输入:s = "06" +输出:0 +解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。 +``` + + **提示:** + +* `1 <= s.length <= 100` +* `s` 只包含数字,并且可能包含前导零。 + +--- + +动态规划: + +`dp[i]` 指前 `i` 个数可行的总数。初始化 `dp[0] = 1`,状态转移方程为 $dp[i] = \left\{ \begin{array}{rcl} dp[i] += dp[i - 1] & dp[i - 1] != '0' \\ dp[i] += dp[i - 2] & dp[i - 2] != '0' \&\& num <= 26 \end{array}\right.$ + +```Java +class Solution { + public int numDecodings(String s) { + int n = s.length(); + int[] dp = new int[n + 1]; + dp[0] = 1; + for(int i = 1; i <= n; i++){ + if(s.charAt(i - 1) != '0') dp[i] += dp[i - 1]; + if(i > 1 && s.charAt(i - 2) != '0'){ + int temp = (s.charAt(i - 2) - '0') * 10 + s.charAt(i - 1) - '0'; + if(temp <= 26) dp[i] += dp[i - 2]; + } + } + return dp[n]; + } +} +``` \ No newline at end of file diff --git "a/Dynamic Programming/931. \344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.md" "b/Dynamic Programming/931. \344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.md" new file mode 100644 index 0000000..3eb352d --- /dev/null +++ "b/Dynamic Programming/931. \344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.md" @@ -0,0 +1,62 @@ +#### 931. 下降路径最小和 + +难度:中等 + +--- + +给你一个 `n x n` 的 **方形** 整数数组 `matrix` ,请你找出并返回通过 `matrix` 的 **下降路径** 的 **最小和** 。 + + **下降路径** 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 `(row, col)` 的下一个元素应当是 `(row + 1, col - 1)`、`(row + 1, col)` 或者 `(row + 1, col + 1)` 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/11/03/failing1-grid.jpg) + +``` +输入:matrix = [[2,1,3],[6,5,4],[7,8,9]] +输出:13 +解释:如图所示,为和最小的两条下降路径 +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/11/03/failing2-grid.jpg) + +``` +输入:matrix = [[-19,57],[-40,-5]] +输出:-59 +解释:如图所示,为和最小的下降路径 +``` + + **提示:** + +* `n == matrix.length == matrix[i].length` +* `1 <= n <= 100` +* `-100 <= matrix[i][j] <= 100` + +--- + +二维动态规划: + +可以发现,坐标为 `[x, y]` 的最小路径和为 `min([x - 1][y - 1], [x - 1][y], [x - 1][y + 1]) + matrix[x][y]`,相当于状态转移函数。那么用一个dp数组记录即可求出末行每个元素的和最小下降路径。 + +```Java +class Solution { + public int minFallingPathSum(int[][] matrix) { + int n = matrix.length; + int[][] dp = new int[n][n]; + for(int j = 0; j < n; j++){ + dp[0][j] = matrix[0][j]; + } + for(int i = 1; i < n; i++){ + for(int j = 0; j < n; j++){ + int minn = dp[i - 1][j]; + if(j >= 1) minn = Math.min(minn, dp[i - 1][j - 1]); + if(j < n - 1) minn = Math.min(minn, dp[i - 1][j + 1]); + dp[i][j] = minn + matrix[i][j]; + } + } + return Arrays.stream(dp[n - 1]).min().getAsInt(); + } +} +``` \ No newline at end of file diff --git a/Dynamic Programming/README.md b/Dynamic Programming/README.md index 9585582..8c28c5a 100644 --- a/Dynamic Programming/README.md +++ b/Dynamic Programming/README.md @@ -2,6 +2,6 @@ 一维状态转移函数分以下两种情况: -- `dp[i]` 表示以第 i 个数组结尾的最大/小情况。一定是包含当前第 i 个数组的情况。而求解 `dp[i]` 往往需要向前遍历查找符合情况的 `dp` 数组,数组长度一般为 `n`,最后的结果是遍历 `dp` 数组,取数组的最大/小值。 +- `dp[i]` 表示以第 i 个数组结尾的最大/小情况。一定是包含当前第 i 个数组的情况。而求解 `dp[i]` 往往需要向前遍历查找符合情况的 `dp` 数组,数组长度一般为 `n`,最后的结果是遍历 `dp` 数组,取数组的最大/小值。例如,[121. 买卖股票的最佳时机](./121.%20买卖股票的最佳时机.md)。 -- `dp[i]` 表示前 i 个数组的最大/小情况。不一定是包含当前第 i 个数组的情况。数组长度一般为 `n + 1`,最后的结果一定是 `dp[n]` \ No newline at end of file +- `dp[i]` 表示前 i 个数组的最大/小情况。不一定是包含当前第 i 个数组的情况。数组长度一般为 `n + 1`,最后的结果一定是 `dp[n]`。例如,[122. 买卖股票的最佳时机 II.md](./122.%20买卖股票的最佳时机%20II.md)。 \ No newline at end of file diff --git "a/Dynamic Programming/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" "b/Dynamic Programming/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" new file mode 100644 index 0000000..60d1e87 --- /dev/null +++ "b/Dynamic Programming/\345\211\221\346\214\207 Offer 47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" @@ -0,0 +1,55 @@ +#### 剑指 Offer 47. 礼物的最大价值 + +难度:中等 + +--- + +在一个 m\*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物? + + **示例 1:** + +``` +输入: +[ +  [1,3,1], +  [1,5,1], +  [4,2,1] +] +输出: 12 +解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物 +``` + +提示: + +* `0 < grid.length <= 200` +* `0 < grid[0].length <= 200` + +--- + +动态规划: + +二维数组初始化:`i = 0` 或 `j = 0` 时的数组 `dp[i][j]` 为对应的 `grid[i][j]` 的值; + +状态转移方程也比较简单:`f(i, j) = max{f(i - 1, j), f(i, j - 1)} + grid[i][j]`。 + +```Java +class Solution { + public int maxValue(int[][] grid) { + int m = grid.length, n = grid[0].length; + int[][] dp = new int[m][n]; + dp[0][0] = grid[0][0]; + for(int i = 1; i < m; i++){ + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + for(int j = 1; j < n; j++){ + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + for(int i = 1; i < m; i++){ + for(int j = 1; j < n; j++){ + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; + } + } + return dp[m - 1][n - 1]; + } +} +``` \ No newline at end of file diff --git "a/Enumerate/5. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" "b/Enumerate/5. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" new file mode 100644 index 0000000..f2ad6aa --- /dev/null +++ "b/Enumerate/5. \346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.md" @@ -0,0 +1,63 @@ +#### 5. 最长回文子串 + +难度:中等 + +--- + +给你一个字符串 `s`,找到 `s` 中最长的回文子串。 + +如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 + + **示例 1:** + +``` +输入:s = "babad" +输出:"bab" +解释:"aba" 同样是符合题意的答案。 +``` + + **示例 2:** + +``` +输入:s = "cbbd" +输出:"bb" +``` + + **提示:** + +* `1 <= s.length <= 1000` +* `s` 仅由数字和英文字母组成 + +--- + +二维动态规划: + +`dp[i][j]` 表示从下标 `i` 到下标 `j` 范围内的字符串是否是回文。状态转移方程为 $dp[i][j] = (dp[i + 1][j - 1] ~\&~ s[i] == s[j]) ~\|~ j - i == 1$,初始化 `dp[i][i] = true`。所以外循环是从 `j` 开始,内循环是 `i`。 + +```Java +class Solution { + public String longestPalindrome(String s) { + int n = s.length(), index = 0, maxn = 1; + boolean[][] dp = new boolean[n][n]; + for(int i = 0; i < n; i++){ + dp[i][i] = true; + } + for(int j = 1; j < n; j++){ + for(int i = 0; i < j; i++){ + if(s.charAt(i) != s.charAt(j)){ + dp[i][j] = false; + } else if(j - i == 1){ + dp[i][j] = true; + } else { + dp[i][j] = dp[i + 1][j - 1]; + } + if(dp[i][j] && j - i + 1 > maxn){ + maxn = j - i + 1; + index = i; + } + } + } + return s.substring(index, index + maxn); + } +} +``` \ No newline at end of file diff --git "a/Enumerate/78. \345\255\220\351\233\206.md" "b/Enumerate/78. \345\255\220\351\233\206.md" new file mode 100644 index 0000000..89e2acb --- /dev/null +++ "b/Enumerate/78. \345\255\220\351\233\206.md" @@ -0,0 +1,53 @@ +#### 78. 子集 + +难度:中等 + +--- + +给你一个整数数组 `nums` ,数组中的元素 **互不相同** 。返回该数组所有可能的子集(幂集)。 + +解集 **不能** 包含重复的子集。你可以按 **任意顺序** 返回解集。 + + **示例 1:** + +``` +输入:nums = [1,2,3] +输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] +``` + + **示例 2:** + +``` +输入:nums = [0] +输出:[[],[0]] +``` + + **提示:** + +* `1 <= nums.length <= 10` +* `-10 <= nums[i] <= 10` +* `nums` 中的所有元素 **互不相同** + +--- + +循环枚举: + +规律:空集的幂集只有空集,每增加一个元素,让之前幂集中的每个集合,追加这个元素,就是新增的子集。 + +```Java +class Solution { + public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + res.add(new ArrayList()); + for(int num: nums){ + int sz = res.size(); + for(int i = 0; i < sz; i++){ + List temp = new ArrayList<>(res.get(i)); + temp.add(num); + res.add(temp); + } + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Enumerate/970. \345\274\272\346\225\264\346\225\260.md" "b/Enumerate/970. \345\274\272\346\225\264\346\225\260.md" new file mode 100644 index 0000000..8a4660f --- /dev/null +++ "b/Enumerate/970. \345\274\272\346\225\264\346\225\260.md" @@ -0,0 +1,61 @@ +#### 970. 强整数 + +难度:中等 + +--- + +给定三个整数 `x` 、 `y` 和 `bound` ,返回 _值小于或等于 `bound` 的所有  **强整数**  组成的列表_ 。 + +如果某一整数可以表示为 `x^i + y^j` ,其中整数 `i >= 0` 且 `j >= 0`,那么我们认为该整数是一个  **强整数**  。 + +你可以按 **任何顺序** 返回答案。在你的回答中,每个值 **最多** 出现一次。 + + **示例 1:** + +``` +输入:x = 2, y = 3, bound = 10 +输出:[2,3,4,5,7,9,10] +解释: +2 = 20 + 30 +3 = 21 + 30 +4 = 20 + 31 +5 = 21 + 31 +7 = 22 + 31 +9 = 23 + 30 +10 = 20 + 32 +``` + + **示例 2:** + +``` +输入:x = 3, y = 5, bound = 15 +输出:[2,4,6,8,10,14] +``` + + **提示:** + +* `1 <= x, y <= 100` +* `0 <= bound <= 10^6` + +--- + +枚举: + +控制循环的范围即可,并且需要用 `HashSet` 去除重复答案。 + +```Java +class Solution { + public List powerfulIntegers(int x, int y, int bound) { + Set set = new HashSet(); + for(int i = 1; i <= bound; i *= x){ + for(int j = 1; i + j <= bound; j *= y){ + set.add(i + j); + if(y == 1) break; + } + if(x == 1) break; + + } + return new ArrayList(set); + } +} +``` \ No newline at end of file diff --git "a/Graph/1615. \346\234\200\345\244\247\347\275\221\347\273\234\347\247\251.md" "b/Graph/1615. \346\234\200\345\244\247\347\275\221\347\273\234\347\247\251.md" new file mode 100644 index 0000000..270208d --- /dev/null +++ "b/Graph/1615. \346\234\200\345\244\247\347\275\221\347\273\234\347\247\251.md" @@ -0,0 +1,81 @@ +#### 1615. 最大网络秩 + +难度:中等 + +--- + +`n` 座城市和一些连接这些城市的道路 `roads` 共同组成一个基础设施网络。每个 `roads[i] = [ai, bi]` 都表示在城市 `ai` 和 `bi` 之间有一条双向道路。 + +两座不同城市构成的 **城市对** 的 **网络秩** 定义为:与这两座城市 **直接** 相连的道路总数。如果存在一条道路直接连接这两座城市,则这条道路只计算 **一次** 。 + +整个基础设施网络的 **最大网络秩** 是所有不同城市对中的 **最大网络秩** 。 + +给你整数 `n` 和数组 `roads`,返回整个基础设施网络的 **最大网络秩** 。 + + **示例 1:** + + **![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/10/11/ex1.png)** + +``` +输入:n = 4, roads = [[0,1],[0,3],[1,2],[1,3]] +输出:4 +解释:城市 0 和 1 的网络秩是 4,因为共有 4 条道路与城市 0 或 1 相连。位于 0 和 1 之间的道路只计算一次。 +``` + + **示例 2:** + + **![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/10/11/ex2.png)** + +``` +输入:n = 5, roads = [[0,1],[0,3],[1,2],[1,3],[2,3],[2,4]] +输出:5 +解释:共有 5 条道路与城市 1 或 2 相连。 +``` + + **示例 3:** + +``` +输入:n = 8, roads = [[0,1],[1,2],[2,3],[2,4],[5,6],[5,7]] +输出:5 +解释:2 和 5 的网络秩为 5,注意并非所有的城市都需要连接起来。 +``` + + **提示:** + +* `2 <= n <= 100` +* `0 <= roads.length <= n * (n - 1) / 2` +* `roads[i].length == 2` +* `0 <= ai, bi <= n-1` +* `ai != bi` +* 每对城市之间 **最多只有一条**  道路相连 + +--- + +邻接表: + +构造邻接表再遍历所有点即可。 + +```Java +class Solution { + public int maximalNetworkRank(int n, int[][] roads) { + List[] arraylist = new List[n]; + for(int i = 0; i < n; i++){ + arraylist[i] = new ArrayList<>(); + } + for(int i = 0; i < roads.length; i++){ + int a = roads[i][0], b = roads[i][1]; + arraylist[a].add(b); + arraylist[b].add(a); + } + int ans = 0; + for(int i = 0; i < n - 1; i++){ + for(int j = i + 1; j < n; j++){ + int temp = arraylist[i].size() + arraylist[j].size(); + if(arraylist[i].contains(j)) temp--; + ans = Math.max(ans, temp); + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/1033. \347\247\273\345\212\250\347\237\263\345\255\220\347\233\264\345\210\260\350\277\236\347\273\255.md" "b/Greedy/1033. \347\247\273\345\212\250\347\237\263\345\255\220\347\233\264\345\210\260\350\277\236\347\273\255.md" new file mode 100644 index 0000000..e88d14f --- /dev/null +++ "b/Greedy/1033. \347\247\273\345\212\250\347\237\263\345\255\220\347\233\264\345\210\260\350\277\236\347\273\255.md" @@ -0,0 +1,58 @@ +#### 1033. 移动石子直到连续 + +难度:中等 + +--- + +三枚石子放置在数轴上,位置分别为 `a`,`b`,`c`。 + +每一回合,你可以从两端之一拿起一枚石子(位置最大或最小),并将其放入两端之间的任一空闲位置。形式上,假设这三枚石子当前分别位于位置 `x, y, z` 且 `x < y < z`。那么就可以从位置 `x` 或者是位置 `z` 拿起一枚石子,并将该石子移动到某一整数位置 `k` 处,其中 `x < k < z` 且 `k != y`。 + +当你无法进行任何移动时,即,这些石子的位置连续时,游戏结束。 + +要使游戏结束,你可以执行的最小和最大移动次数分别是多少? 以长度为 2 的数组形式返回答案:`answer = [minimum_moves, maximum_moves]` + + **示例 1:** + +``` +输入:a = 1, b = 2, c = 5 +输出:[1, 2] +解释:将石子从 5 移动到 4 再移动到 3,或者我们可以直接将石子移动到 3。 +``` + + **示例 2:** + +``` +输入:a = 4, b = 3, c = 2 +输出:[0, 0] +解释:我们无法进行任何移动。 +``` + + **提示:** + +1. `1 <= a <= 100` +2. `1 <= b <= 100` +3. `1 <= c <= 100` +4. `a != b, b != c, c != a` + +--- + +贪心: + +分类讨论,当三个数连续时,显然最小步骤为零;如果三个数中有两个数连续或者差为二,则最小步骤为一,因为将另一个数移动至另一边或中间;其他情况最小步骤为二。另外,不管什么情况下,最大步骤都是两边数相减再减二。 + +```Java +class Solution { + public int[] numMovesStones(int a, int b, int c) { + int maxn = Math.max(Math.max(a, b), c); + int minn = Math.min(Math.min(a, b), c); + int midd = a + b + c - maxn - minn; + int[] ans = new int[2]; + if(maxn - midd == 1 && midd - minn == 1) ans[0] = 0; + else if(maxn - midd <= 2 || midd - minn <= 2) ans[0] = 1; + else ans[0] = 2; + ans[1] = maxn - minn - 2; + return ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/1053. \344\272\244\346\215\242\344\270\200\346\254\241\347\232\204\345\205\210\345\211\215\346\216\222\345\210\227.md" "b/Greedy/1053. \344\272\244\346\215\242\344\270\200\346\254\241\347\232\204\345\205\210\345\211\215\346\216\222\345\210\227.md" new file mode 100644 index 0000000..0502b84 --- /dev/null +++ "b/Greedy/1053. \344\272\244\346\215\242\344\270\200\346\254\241\347\232\204\345\205\210\345\211\215\346\216\222\345\210\227.md" @@ -0,0 +1,69 @@ +#### 1053. 交换一次的先前排列 + +难度:中等 + +--- + +给你一个正整数数组 `arr`(可能存在重复的元素),请你返回可在  **一次交换** (交换两数字 `arr[i]` 和 `arr[j]` 的位置)后得到的、按字典序排列小于 `arr` 的最大排列。 + +如果无法这么操作,就请返回原数组。 + + **示例 1:** + +``` +输入:arr = [3,2,1] +输出:[3,1,2] +解释:交换 2 和 1 +``` + + **示例 2:** + +``` +输入:arr = [1,1,5] +输出:[1,1,5] +解释:已经是最小排列 +``` + + **示例 3:** + +``` +输入:arr = [1,9,4,6,7] +输出:[1,7,4,6,9] +解释:交换 9 和 7 +``` + + **提示:** + +* `1 <= arr.length <= 10^4` +* `1 <= arr[i] <= 10^4` + +--- + +贪心: + +1. 首先,当前序列开始从后往前找,找到第一个降序的位置(`A[i]>A[i+1]`),则必存在能构造比当前小的序列。 + +2. 接着,在 `A[i]` 后面的数字中,找出小于 `A[i]` 且最接近 `A[i]` 的值的数字并和 `A[i]` 交换,经过第一步的倒序遍历后,`A[i]` 后面的数字一定是**非降序**的。 + +```Java +class Solution { + public int[] prevPermOpt1(int[] arr) { + int n = arr.length; + for(int i = n - 2; i >= 0; i--){ + if(arr[i] > arr[i + 1]){ + int j = n - 1; + while(arr[j] >= arr[i] || arr[j] == arr[j - 1]) j--; + swap(arr, i , j); + break; + } + } + return arr; + } + + private void swap(int[] arr, int i, int j){ + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} +``` \ No newline at end of file diff --git "a/Greedy/1465. \345\210\207\345\211\262\345\220\216\351\235\242\347\247\257\346\234\200\345\244\247\347\232\204\350\233\213\347\263\225.md" "b/Greedy/1465. \345\210\207\345\211\262\345\220\216\351\235\242\347\247\257\346\234\200\345\244\247\347\232\204\350\233\213\347\263\225.md" new file mode 100644 index 0000000..64981f6 --- /dev/null +++ "b/Greedy/1465. \345\210\207\345\211\262\345\220\216\351\235\242\347\247\257\346\234\200\345\244\247\347\232\204\350\233\213\347\263\225.md" @@ -0,0 +1,74 @@ +#### 1465. 切割后面积最大的蛋糕 + +难度:中等 + +--- + +矩形蛋糕的高度为 `h` 且宽度为 `w`,给你两个整数数组 `horizontalCuts` 和 `verticalCuts`,其中: + +*  `horizontalCuts[i]` 是从矩形蛋糕顶部到第  `i` 个水平切口的距离 +* `verticalCuts[j]` 是从矩形蛋糕的左侧到第 `j` 个竖直切口的距离 + +请你按数组 _`horizontalCuts`_ 和 _`verticalCuts`_ 中提供的水平和竖直位置切割后,请你找出 **面积最大** 的那份蛋糕,并返回其 **面积** 。由于答案可能是一个很大的数字,因此需要将结果  **对**  `10^9 + 7`  **取余** 后返回。 + + **示例 1:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/05/30/leetcode_max_area_2.png) + +``` +输入:h = 5, w = 4, horizontalCuts = [1,2,4], verticalCuts = [1,3] +输出:4 +解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色的那份蛋糕面积最大。 +``` + + **示例 2:** + + **![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/05/30/leetcode_max_area_3.png)** + +``` +输入:h = 5, w = 4, horizontalCuts = [3,1], verticalCuts = [1] +输出:6 +解释:上图所示的矩阵蛋糕中,红色线表示水平和竖直方向上的切口。切割蛋糕后,绿色和黄色的两份蛋糕面积最大。 +``` + + **示例 3:** + +``` +输入:h = 5, w = 4, horizontalCuts = [3], verticalCuts = [3] +输出:9 +``` + + **提示:** + +* `2 <= h, w <= 10^9` +* `1 <= horizontalCuts.length <= min(h - 1, 10^5)` +* `1 <= verticalCuts.length <= min(w - 1, 10^5)` +* `1 <= horizontalCuts[i] < h` +* `1 <= verticalCuts[i] < w` +* 题目数据保证 `horizontalCuts` 中的所有元素各不相同 +* 题目数据保证 `verticalCuts` 中的所有元素各不相同 + +--- + +贪心: + +取横轴和纵轴的最大值并相乘即可,注意相乘后的结果可能越界,所以先转换为 `long` 类型再取余。 + +```Java +class Solution { + public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) { + Arrays.sort(horizontalCuts); + Arrays.sort(verticalCuts); + return (int) ((long) calculate(horizontalCuts, h) * calculate(verticalCuts, w) % 1000000007); + } + + private int calculate(int[] cuts, int length){ + int pre = 0, res = 0; + for(int cut: cuts){ + res = Math.max(res, cut - pre); + pre = cut; + } + return Math.max(res, length - pre); + } +} +``` \ No newline at end of file diff --git "a/Greedy/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" "b/Greedy/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" new file mode 100644 index 0000000..6b5a1ff --- /dev/null +++ "b/Greedy/1605. \347\273\231\345\256\232\350\241\214\345\222\214\345\210\227\347\232\204\345\222\214\346\261\202\345\217\257\350\241\214\347\237\251\351\230\265.md" @@ -0,0 +1,89 @@ +#### 1605. 给定行和列的和求可行矩阵 + +难度:中等 + +--- + +给你两个非负整数数组 `rowSum` 和 `colSum` ,其中 `rowSum[i]` 是二维矩阵中第 `i` 行元素的和, `colSum[j]` 是第 `j` 列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。 + +请找到大小为 `rowSum.length x colSum.length` 的任意 **非负整数**  矩阵,且该矩阵满足 `rowSum` 和 `colSum` 的要求。 + +请你返回任意一个满足题目要求的二维矩阵,题目保证存在 **至少一个**  可行矩阵。 + + **示例 1:** + +``` +输入:rowSum = [3,8], colSum = [4,7] +输出:[[3,0], + [1,7]] +解释: +第 0 行:3 + 0 = 3 == rowSum[0] +第 1 行:1 + 7 = 8 == rowSum[1] +第 0 列:3 + 1 = 4 == colSum[0] +第 1 列:0 + 7 = 7 == colSum[1] +行和列的和都满足题目要求,且所有矩阵元素都是非负的。 +另一个可行的矩阵为:[[1,2], + [3,5]] +``` + + **示例 2:** + +``` +输入:rowSum = [5,7,10], colSum = [8,6,8] +输出:[[0,5,0], + [6,1,0], + [2,0,8]] +``` + + **示例 3:** + +``` +输入:rowSum = [14,9], colSum = [6,9,8] +输出:[[0,9,5], + [6,0,3]] +``` + + **示例 4:** + +``` +输入:rowSum = [1,0], colSum = [1] +输出:[[1], + [0]] +``` + + **示例 5:** + +``` +输入:rowSum = [0], colSum = [0] +输出:[[0]] +``` + + **提示:** + +* `1 <= rowSum.length, colSum.length <= 500` +* `0 <= rowSum[i], colSum[i] <= 10^8` +* `sum(rowSum) == sum(colSum)` + +--- + +贪心: + +运筹学中的运输问题。答案不唯一。有西北角法、最小元素法等,以下是西北角法的代码,即从**左上角**开始分配。 + +```Java +class Solution { + public int[][] restoreMatrix(int[] rowSum, int[] colSum) { + int i = 0, j = 0; + int[][] ans = new int[rowSum.length][colSum.length]; + while(i < rowSum.length && j < colSum.length){ + int temp = Math.min(rowSum[i], colSum[j]); + ans[i][j] = temp; + rowSum[i] -= temp; + colSum[j] -= temp; + if(rowSum[i] == 0) i++; + if(colSum[j] == 0) j++; + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/2611. \350\200\201\351\274\240\345\222\214\345\245\266\351\205\252.md" "b/Greedy/2611. \350\200\201\351\274\240\345\222\214\345\245\266\351\205\252.md" new file mode 100644 index 0000000..daf9a88 --- /dev/null +++ "b/Greedy/2611. \350\200\201\351\274\240\345\222\214\345\245\266\351\205\252.md" @@ -0,0 +1,92 @@ +#### 2611. 老鼠和奶酪 + +难度:中等 + +--- + +有两只老鼠和 `n` 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。 + +下标为 `i` 处的奶酪被吃掉的得分为: + +* 如果第一只老鼠吃掉,则得分为 `reward1[i]` 。 +* 如果第二只老鼠吃掉,则得分为 `reward2[i]` 。 + +给你一个正整数数组 `reward1` ,一个正整数数组 `reward2` ,和一个非负整数 `k` 。 + +请你返回第一只老鼠恰好吃掉 `k` 块奶酪的情况下, **最大**  得分为多少。 + + **示例 1:** + +``` +输入:reward1 = [1,1,3,4], reward2 = [4,4,1,1], k = 2 +输出:15 +解释:这个例子中,第一只老鼠吃掉第 2 和 3 块奶酪(下标从 0 开始),第二只老鼠吃掉第 0 和 1 块奶酪。 +总得分为 4 + 4 + 3 + 4 = 15 。 +15 是最高得分。 +``` + + **示例 2:** + +``` +输入:reward1 = [1,1], reward2 = [1,1], k = 2 +输出:2 +解释:这个例子中,第一只老鼠吃掉第 0 和 1 块奶酪(下标从 0 开始),第二只老鼠不吃任何奶酪。 +总得分为 1 + 1 = 2 。 +2 是最高得分。 +``` + + **提示:** + +* `1 <= n == reward1.length == reward2.length <= 10^5` +* `1 <= reward1[i], reward2[i] <= 1000` +* `0 <= k <= n` + +--- + +方法一:排序 + 贪心: + +第一只老鼠吃掉 k 个奶酪,剩下的给第二只老鼠吃,意味着这 k 个奶酪一定是差值最大的奶酪。那么以第二只老鼠的得分为基准,那么再加上 k 个得分差即可。 + +```java +class Solution { + public int miceAndCheese(int[] reward1, int[] reward2, int k) { + int n = reward1.length, ans = 0; + int[] diff = new int[n]; + for(int i = 0; i < n; i++){ + diff[i] = reward1[i] - reward2[i]; + ans += reward2[i]; + } + Arrays.sort(diff); + for(int i = 0; i < k; i++){ + ans += diff[n - i - 1]; + } + return ans; + } +} +``` + + + +方法二:优先队列 + 贪心: + +本题类似于**选择 k 个最大值**,那么维护一个长度为 k 的**最小堆**(优先队列),那么堆中存放的元素即是整个数组中的 k 个最大值。 + +```Java +class Solution { + public int miceAndCheese(int[] reward1, int[] reward2, int k) { + int n = reward1.length, ans = 0; + PriorityQueue pq = new PriorityQueue<>(); + for(int i = 0; i < n; i++){ + ans += reward2[i]; + pq.offer(reward1[i] - reward2[i]); + if(pq.size() > k){ + pq.poll(); + } + } + while(!pq.isEmpty()){ + ans += pq.poll(); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" "b/Greedy/435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" new file mode 100644 index 0000000..e7a79ac --- /dev/null +++ "b/Greedy/435. \346\227\240\351\207\215\345\217\240\345\214\272\351\227\264.md" @@ -0,0 +1,59 @@ +#### 435. 无重叠区间 + +难度:中等 + +--- + +给定一个区间的集合 `intervals` ,其中 `intervals[i] = [starti, endi]` 。返回 _需要移除区间的最小数量,使剩余区间互不重叠_ 。 + + **示例 1:** + +``` +输入: intervals = [[1,2],[2,3],[3,4],[1,3]] +输出: 1 +解释: 移除 [1,3] 后,剩下的区间没有重叠。 +``` + + **示例 2:** + +``` +输入: intervals = [ [1,2], [1,2], [1,2] ] +输出: 2 +解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 +``` + + **示例 3:** + +``` +输入: intervals = [ [1,2], [2,3] ] +输出: 0 +解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 +``` + + **提示:** + +* `1 <= intervals.length <= 10^5` +* `intervals[i].length == 2` +* `-5 * 10^4 <= starti < endi <= 5 * 10^4` + +--- + +经典贪心算法: + +区间右端点排序!!!然后记录最大不重叠区间,最后用总数减去该不重叠区间数即可。 + +```Java +class Solution { + public int eraseOverlapIntervals(int[][] intervals) { + Arrays.sort(intervals, (a, b) -> (a[1] - b[1])); + int n = intervals.length, right = intervals[0][1], ans = 1; + for(int i = 1; i < n; i++){ + if(intervals[i][0] >= right){ + ans++; + right = intervals[i][1]; + } + } + return n - ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" "b/Greedy/452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" new file mode 100644 index 0000000..fb69b77 --- /dev/null +++ "b/Greedy/452. \347\224\250\346\234\200\345\260\221\346\225\260\351\207\217\347\232\204\347\256\255\345\274\225\347\210\206\346\260\224\347\220\203.md" @@ -0,0 +1,71 @@ +#### 452. 用最少数量的箭引爆气球 + +难度:中等 + +--- + +有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 `points` ,其中`points[i] = [xstart, xend]` 表示水平直径在 `xstart` 和 `xend`之间的气球。你不知道气球的确切 y 坐标。 + +一支弓箭可以沿着 x 轴从不同点 **完全垂直** 地射出。在坐标 `x` 处射出一支箭,若有一个气球的直径的开始和结束坐标为 `x``start`,`x``end`, 且满足  `xstart ≤ x ≤ x``end`,则该气球会被 **引爆**  。可以射出的弓箭的数量 **没有限制** 。 弓箭一旦被射出之后,可以无限地前进。 + +给你一个数组 `points` ,_返回引爆所有气球所必须射出的 **最小** 弓箭数_ 。 + +  + + **示例 1:** + +``` +输入:points = [[10,16],[2,8],[1,6],[7,12]] +输出:2 +解释:气球可以用2支箭来爆破: +-在x = 6处射出箭,击破气球[2,8]和[1,6]。 +-在x = 11处发射箭,击破气球[10,16]和[7,12]。 +``` + + **示例 2:** + +``` +输入:points = [[1,2],[3,4],[5,6],[7,8]] +输出:4 +解释:每个气球需要射出一支箭,总共需要4支箭。 +``` + + **示例 3:** + +``` +输入:points = [[1,2],[2,3],[3,4],[4,5]] +输出:2 +解释:气球可以用2支箭来爆破: +- 在x = 2处发射箭,击破气球[1,2]和[2,3]。 +- 在x = 4处射出箭,击破气球[3,4]和[4,5]。 +``` + + **提示:** + +* `1 <= points.length <= 10^5` +* `points[i].length == 2` +* `-2^31 <= xstart < xend <= 2^31 - 1` + +--- + +排序 + 贪心: + +将右端点以升序排序,每根弓箭都是取右端点值最小的点(贪心),同时不考虑左端点比该点小的气球(即弓箭能引爆该气球),直到取完所有点为止。 + +```Java +class Solution { + public int findMinArrowShots(int[][] points) { + Arrays.sort(points, (a, b) -> { + return Integer.compare(a[1], b[1]); + }); + int ans = 1, last = points[0][1]; + for(int[] temp: points){ + if(temp[0] > last){ + ans++; + last = temp[1]; + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Greedy/763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" "b/Greedy/763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" new file mode 100644 index 0000000..91c8bb6 --- /dev/null +++ "b/Greedy/763. \345\210\222\345\210\206\345\255\227\346\257\215\345\214\272\351\227\264.md" @@ -0,0 +1,65 @@ +#### 763. 划分字母区间 + +难度:中等 + +--- + +给你一个字符串 `s` 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 + +注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 `s` 。 + +返回一个表示每个字符串片段的长度的列表。 + + **示例 1:** +``` +输入:s = "ababcbacadefegdehijhklij" +输出:[9,7,8] +解释: +划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 +每个字母最多出现在一个片段中。 +像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 +``` + + **示例 2:** + +``` +输入:s = "eccbbbbdec" +输出:[10] +``` + + **提示:** + +* `1 <= s.length <= 500` +* `s` 仅由小写英文字母组成 + +--- + +贪心: + +由于同一个字母只能出现在同一个片段,显然同一个字母的第一次出现的下标位置和最后一次出现的下标位置必须出现在同一个片段。 + +因此先顺序遍历,记录每一个字母出现的最大下标处。 + +接着再顺序遍历一遍,遍历的过程中将指针**移动当前字母出现的最大下标处**。如果指针刚好是当前下标,则意味着从起始位置到当前位置是一个符合要求的答案,接着更新起始位置下标继续遍历。 + +```Java +class Solution { + public List partitionLabels(String s) { + int n = s.length(); + int[] alphabet = new int[26]; + for(int i = 0; i < n; i++){ + alphabet[s.charAt(i) - 'a'] = i; + } + List res = new ArrayList<>(); + int start = 0, end = 0; + for(int i = 0; i < n; i++){ + end = Math.max(end, alphabet[s.charAt(i) - 'a']); // 直接将尾指针移到了当前字母出现的最大下标处 + if(i == end){ + res.add(end - start + 1); + start = end + 1; + } + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Hash Table/128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" "b/Hash Table/128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" new file mode 100644 index 0000000..e1ce15e --- /dev/null +++ "b/Hash Table/128. \346\234\200\351\225\277\350\277\236\347\273\255\345\272\217\345\210\227.md" @@ -0,0 +1,58 @@ +#### 128. 最长连续序列 + +难度:中等 + +--- + +给定一个未排序的整数数组 `nums` ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 + +请你设计并实现时间复杂度为 `O(n)` 的算法解决此问题。 + + **示例 1:** + +``` +输入:nums = [100,4,200,1,3,2] +输出:4 +解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 +``` + + **示例 2:** + +``` +输入:nums = [0,3,7,2,5,8,4,6,0,1] +输出:9 +``` + + **提示:** + +* `0 <= nums.length <= 10^5` +* `-10^9 <= nums[i] <= 10^9` + +--- + +哈希集合: + +遍历集合中的每个元素,如果是一串连续数字的第一个数字(例如,`3456` 中的 `3`),那么继续使用 `contains()` 方法在集合中查找后续数字,直到结束;否则跳过该元素。 + +```Java +class Solution { + public int longestConsecutive(int[] nums) { + int ans = 0, n = nums.length; + HashSet hashset = new HashSet<>(); + for(int i = 0; i < n; i++){ + hashset.add(nums[i]); + } + for(int num: hashset){ + if(!hashset.contains(num - 1)){ + int temp = 1; + while(hashset.contains(num + 1)){ + num++; + temp++; + } + ans = Math.max(ans, temp); + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Hash Table/138. \351\232\217\346\234\272\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" "b/Hash Table/138. \351\232\217\346\234\272\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" new file mode 100644 index 0000000..7082102 --- /dev/null +++ "b/Hash Table/138. \351\232\217\346\234\272\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" @@ -0,0 +1,115 @@ +#### 138. 随机链表的复制 + +难度:中等 + +--- + +给你一个长度为 `n` 的链表,每个节点包含一个额外增加的随机指针 `random` ,该指针可以指向链表中的任何节点或空节点。 + +构造这个链表的  **[深拷贝](https://baike.baidu.com/item/深拷贝/22785317?fr=aladdin)** 。 深拷贝应该正好由 `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:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e1.png) + +``` +输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] +输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] +``` + + **示例 2:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e2.png) + +``` +输入:head = [[1,1],[2,1]] +输出:[[1,1],[2,1]] +``` + + **示例 3:** + + **![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/09/e3.png)** + +``` +输入:head = [[3,null],[3,0],[3,null]] +输出:[[3,null],[3,0],[3,null]] +``` + + **提示:** + +* `0 <= n <= 1000` +* `-10^4 <= Node.val <= 10^4` +* `Node.random` 为 `null` 或指向链表中的节点。 + +--- + +哈希表: + +利用哈希表的查询特点,考虑构建 **原链表节点** 和 **新链表对应节点** 的键值对映射关系,再遍历构建新链表各节点的 `next` 和 `random` 引用指向即可。共两次遍历。 + +```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; + HashMap hashmap = new HashMap<>(); + Node temp = head; + while(temp != null){ + hashmap.put(temp, new Node(temp.val)); + temp = temp.next; + } + temp = head; + while(temp != null){ + hashmap.get(temp).next = hashmap.get(temp.next); + hashmap.get(temp).random = hashmap.get(temp.random); + temp = temp.next; + } + return hashmap.get(head); + } +} + +// 递归 +class Solution { + + HashMap hashmap = new HashMap<>(); + + public Node copyRandomList(Node head) { + if(head == null){ + return null; + } + if(!hashmap.containsKey(head)){ + Node node = new Node(head.val); + hashmap.put(head, node); + node.next = copyRandomList(head.next); + node.random = copyRandomList(head.random); + } + return hashmap.get(head); + } +} +``` \ No newline at end of file diff --git "a/Hash Table/146. LRU \347\274\223\345\255\230.md" "b/Hash Table/146. LRU \347\274\223\345\255\230.md" new file mode 100644 index 0000000..bc8f7c2 --- /dev/null +++ "b/Hash Table/146. LRU \347\274\223\345\255\230.md" @@ -0,0 +1,96 @@ +#### 146. LRU 缓存 + +难度:中等 + +--- + +请你设计并实现一个满足  [LRU (最近最少使用) 缓存](https://baike.baidu.com/item/LRU) 约束的数据结构。 + +实现 `LRUCache` 类: + +* `LRUCache(int capacity)` 以 **正整数** 作为容量 `capacity` 初始化 LRU 缓存 +* `int get(int key)` 如果关键字 `key` 存在于缓存中,则返回关键字的值,否则返回 `-1` 。 +* `void put(int key, int value)` 如果关键字 `key` 已经存在,则变更其数据值 `value` ;如果不存在,则向缓存中插入该组 `key-value` 。如果插入操作导致关键字数量超过 `capacity` ,则应该 **逐出** 最久未使用的关键字。 + +函数 `get` 和 `put` 必须以 `O(1)` 的平均时间复杂度运行。 + + **示例:** + +``` +输入 +["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] +[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] +输出 +[null, null, null, 1, null, -1, null, -1, 3, 4] + +解释 +LRUCache lRUCache = new LRUCache(2); +lRUCache.put(1, 1); // 缓存是 {1=1} +lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} +lRUCache.get(1); // 返回 1 +lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} +lRUCache.get(2); // 返回 -1 (未找到) +lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} +lRUCache.get(1); // 返回 -1 (未找到) +lRUCache.get(3); // 返回 3 +lRUCache.get(4); // 返回 4 +``` + + **提示:** + +* `1 <= capacity <= 3000` +* `0 <= key <= 10000` +* `0 <= value <= 10^5` +* 最多调用 `2 * 10^5` 次 `get` 和 `put` + +--- + +哈希表 + 双向链表: + +![](https://pic.leetcode.cn/1696039105-PSyHej-146-3-c.png) + +```Java +type LRUCache struct { + doubleLinkedList *list.List + hashmap map[int]*list.Element + capacity int +} + +type entry struct { + k, v int +} + +func Constructor(capacity int) LRUCache { + return LRUCache{list.New(), make(map[int]*list.Element), capacity} +} + +func (this *LRUCache) Get(key int) int { + node := this.hashmap[key] + if node == nil { + return -1 + } + this.doubleLinkedList.MoveToFront(node) + return node.Value.(entry).v +} + +func (this *LRUCache) Put(key int, value int) { + node := this.hashmap[key] + if node != nil { + node.Value = entry{key, value} + this.doubleLinkedList.MoveToFront(node) + return + } + this.hashmap[key] = this.doubleLinkedList.PushFront(entry{key, value}) + if this.capacity < len(this.hashmap) { + delete(this.hashmap, this.doubleLinkedList.Remove(this.doubleLinkedList.Back()).(entry).k) + } +} + + +/** + * Your LRUCache object will be instantiated and called as such: + * obj := Constructor(capacity); + * param_1 := obj.Get(key); + * obj.Put(key,value); + */ +``` \ No newline at end of file diff --git "a/Hash Table/1487. \344\277\235\350\257\201\346\226\207\344\273\266\345\220\215\345\224\257\344\270\200.md" "b/Hash Table/1487. \344\277\235\350\257\201\346\226\207\344\273\266\345\220\215\345\224\257\344\270\200.md" new file mode 100644 index 0000000..e1ccd21 --- /dev/null +++ "b/Hash Table/1487. \344\277\235\350\257\201\346\226\207\344\273\266\345\220\215\345\224\257\344\270\200.md" @@ -0,0 +1,92 @@ +#### 1487. 保证文件名唯一 + +难度:中等 + +--- + +给你一个长度为 `n` 的字符串数组 `names` 。你将会在文件系统中创建 `n` 个文件夹:在第 `i` 分钟,新建名为 `names[i]` 的文件夹。 + +由于两个文件 **不能** 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 `(k)` 的形式为新文件夹的文件名添加后缀,其中 `k` 是能保证文件名唯一的 **最小正整数** 。 + +返回长度为 _`n`_ 的字符串数组,其中 `ans[i]` 是创建第 `i` 个文件夹时系统分配给该文件夹的实际名称。 + + **示例 1:** + +``` +输入:names = ["pes","fifa","gta","pes(2019)"] +输出:["pes","fifa","gta","pes(2019)"] +解释:文件系统将会这样创建文件名: +"pes" --> 之前未分配,仍为 "pes" +"fifa" --> 之前未分配,仍为 "fifa" +"gta" --> 之前未分配,仍为 "gta" +"pes(2019)" --> 之前未分配,仍为 "pes(2019)" +``` + + **示例 2:** + +``` +输入:names = ["gta","gta(1)","gta","avalon"] +输出:["gta","gta(1)","gta(2)","avalon"] +解释:文件系统将会这样创建文件名: +"gta" --> 之前未分配,仍为 "gta" +"gta(1)" --> 之前未分配,仍为 "gta(1)" +"gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)" 。 +"avalon" --> 之前未分配,仍为 "avalon" +``` + + **示例 3:** + +``` +输入:names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"] +输出:["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"] +解释:当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。 +``` + + **示例 4:** + +``` +输入:names = ["wano","wano","wano","wano"] +输出:["wano","wano(1)","wano(2)","wano(3)"] +解释:每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。 +``` + + **示例 5:** + +``` +输入:names = ["kaido","kaido(1)","kaido","kaido(1)"] +输出:["kaido","kaido(1)","kaido(2)","kaido(1)(1)"] +解释:注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。 +``` + + **提示:** + +* `1 <= names.length <= 5 * 10^4` +* `1 <= names[i].length <= 20` +* `names[i]` 由小写英文字母、数字和/或圆括号组成。 + +--- + +哈希表: + +比较繁琐。又有点像模拟题,重点在于**后缀名被占用后需要添加新的后缀**。 + +```Java +class Solution { + public String[] getFolderNames(String[] names) { + LinkedHashMap hashmap = new LinkedHashMap<>(); + for(String name: names){ + if(hashmap.containsKey(name)){ + int num = hashmap.get(name); + while(hashmap.containsKey(new StringBuilder(name).append("(").append(num + 1).append(")").toString())) num++; // 在哈希表中查找后缀k值 + hashmap.put(name, num + 1); + hashmap.put(new StringBuilder(name).append("(").append(num + 1).append(")").toString(), 0); + } + else{ + hashmap.put(name, 0); + } + } + Set set = hashmap.keySet(); + return set.toArray(new String[hashmap.size()]); + } +} +``` \ No newline at end of file diff --git "a/Hash Table/2352. \347\233\270\347\255\211\350\241\214\345\210\227\345\257\271.md" "b/Hash Table/2352. \347\233\270\347\255\211\350\241\214\345\210\227\345\257\271.md" new file mode 100644 index 0000000..829719f --- /dev/null +++ "b/Hash Table/2352. \347\233\270\347\255\211\350\241\214\345\210\227\345\257\271.md" @@ -0,0 +1,71 @@ +#### 2352. 相等行列对 + +难度:中等 + +--- + +给你一个下标从 **0** 开始、大小为 `n x n` 的整数矩阵 `grid` ,返回满足 `Ri` 行和 `Cj` 列相等的行列对 `(Ri, Cj)` 的数目_。_ + +如果行和列以相同的顺序包含相同的元素(即相等的数组),则认为二者是相等的。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2022/06/01/ex1.jpg) + +``` +输入:grid = [[3,2,1],[1,7,6],[2,7,7]] +输出:1 +解释:存在一对相等行列对: +- (第 2 行,第 1 列):[2,7,7] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2022/06/01/ex2.jpg) + +``` +输入:grid = [[3,1,2,2],[1,4,4,5],[2,4,2,2],[2,4,2,2]] +输出:3 +解释:存在三对相等行列对: +- (第 0 行,第 0 列):[3,1,2,2] +- (第 2 行, 第 2 列):[2,4,2,2] +- (第 3 行, 第 2 列):[2,4,2,2] +``` + + **提示:** + +* `n == grid.length == grid[i].length` +* `1 <= n <= 200` +* `1 <= grid[i][j] <= 10^5` + +--- + +哈希表: + +将每行的元素以字符串的形式作为哈希表的key,值为key出现的次数;先将每行数据存储在哈希表中,再查看每列数据在哈希表中出现的次数,所有列出现的次数相加即可。 + +或者第二种方法,将矩阵转置后再进行对比。 + +```Java +class Solution { + public int equalPairs(int[][] grid) { + int ans = 0, n = grid.length; + HashMap hashmap = new HashMap<>(); + for(int i = 0; i < n; i++){ + StringBuilder sb = new StringBuilder(); + for(int j = 0; j < n; j++){ + sb.append(grid[i][j] + " "); + } + hashmap.put(sb.toString(), hashmap.getOrDefault(sb.toString(), 0) + 1); + } + for(int j = 0; j < n; j++){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < n; i++){ + sb.append(grid[i][j] + " "); + } + ans += hashmap.getOrDefault(sb.toString(), 0); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Hash Table/2512. \345\245\226\345\212\261\346\234\200\351\241\266\345\260\226\347\232\204 K \345\220\215\345\255\246\347\224\237.md" "b/Hash Table/2512. \345\245\226\345\212\261\346\234\200\351\241\266\345\260\226\347\232\204 K \345\220\215\345\255\246\347\224\237.md" new file mode 100644 index 0000000..f24d27c --- /dev/null +++ "b/Hash Table/2512. \345\245\226\345\212\261\346\234\200\351\241\266\345\260\226\347\232\204 K \345\220\215\345\255\246\347\224\237.md" @@ -0,0 +1,82 @@ +#### 2512. 奖励最顶尖的 K 名学生 + +难度:中等 + +--- + +给你两个字符串数组 `positive_feedback` 和 `negative_feedback` ,分别包含表示正面的和负面的词汇。 **不会**  有单词同时是正面的和负面的。 + +一开始,每位学生分数为 `0` 。每个正面的单词会给学生的分数 **加**  `3` 分,每个负面的词会给学生的分数 **减**   `1` 分。 + +给你 `n` 个学生的评语,用一个下标从 **0**  开始的字符串数组 `report` 和一个下标从 **0**  开始的整数数组 `student_id` 表示,其中 `student_id[i]` 表示这名学生的 ID ,这名学生的评语是 `report[i]` 。每名学生的 ID **互不相同** 。 + +给你一个整数 `k` ,请你返回按照得分  **从高到低**  最顶尖的 `k` 名学生。如果有多名学生分数相同,ID 越小排名越前。 + + **示例 1:** + +``` +输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is studious","the student is smart"], student_id = [1,2], k = 2 +输出:[1,2] +解释: +两名学生都有 1 个正面词汇,都得到 3 分,学生 1 的 ID 更小所以排名更前。 +``` + + **示例 2:** + +``` +输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is not studious","the student is smart"], student_id = [1,2], k = 2 +输出:[2,1] +解释: +- ID 为 1 的学生有 1 个正面词汇和 1 个负面词汇,所以得分为 3-1=2 分。 +- ID 为 2 的学生有 1 个正面词汇,得分为 3 分。 +学生 2 分数更高,所以返回 [2,1] 。 +``` + + **提示:** + +* `1 <= positive_feedback.length, negative_feedback.length <= 10^4` +* `1 <= positive_feedback[i].length, negative_feedback[j].length <= 100` +* `positive_feedback[i]` 和 `negative_feedback[j]` 都只包含小写英文字母。 +* `positive_feedback` 和 `negative_feedback` 中不会有相同单词。 +* `n == report.length == student_id.length` +* `1 <= n <= 10^4` +* `report[i]` 只包含小写英文字母和空格 `' '` 。 +* `report[i]` 中连续单词之间有单个空格隔开。 +* `1 <= report[i].length <= 100` +* `1 <= student_id[i] <= 10^9` +* `student_id[i]` 的值 **互不相同**  。 +* `1 <= k <= n` + +--- + +哈希表 + 排序: + +将每个正面单词和负面单词存入**哈希表**中并分别赋予 `3` 和 `-1` 的分数,将每个同学的报告用 `split()` 方法拆分成单词并计算分数,最后排序选取前 `k` 个即可。 + +```Java +class Solution { + public List topStudents(String[] positive_feedback, String[] negative_feedback, String[] report, int[] student_id, int k) { + HashMap hashmap = new HashMap<>(); + for(String word: positive_feedback){ + hashmap.put(word, 3); + } + for(String word: negative_feedback){ + hashmap.put(word, -1); + } + int n = report.length; + int[][] array = new int[n][2]; + for(int i = 0; i < n; i++){ + for(String word: report[i].split(" ")){ + array[i][0] += hashmap.getOrDefault(word, 0); + array[i][1] = student_id[i]; + } + } + Arrays.sort(array, (int[] a, int[] b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0])); + List ans = new ArrayList<>(); + for(int i = 0; i < k; i++){ + ans.add(array[i][1]); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Hash Table/2661. \346\211\276\345\207\272\345\217\240\346\266\202\345\205\203\347\264\240.md" "b/Hash Table/2661. \346\211\276\345\207\272\345\217\240\346\266\202\345\205\203\347\264\240.md" new file mode 100644 index 0000000..1a1843e --- /dev/null +++ "b/Hash Table/2661. \346\211\276\345\207\272\345\217\240\346\266\202\345\205\203\347\264\240.md" @@ -0,0 +1,68 @@ +#### 2661. 找出叠涂元素 + +难度:中等 + +--- + +给你一个下标从 **0** 开始的整数数组 `arr` 和一个 `m x n` 的整数 **矩阵** `mat` 。`arr` 和 `mat` 都包含范围 `[1,m * n]` 内的 **所有** 整数。 + +从下标 `0` 开始遍历 `arr` 中的每个下标 `i` ,并将包含整数 `arr[i]` 的 `mat` 单元格涂色。 + +请你找出 `arr` 中在 `mat` 的某一行或某一列上都被涂色且下标最小的元素,并返回其下标 `i` 。 + + **示例 1:** + +![image explanation for example 1](https://assets.leetcode.com/uploads/2023/01/18/grid1.jpg) +``` +输入:arr = [1,3,4,2], mat = [[1,4],[2,3]] +输出:2 +解释:遍历如上图所示,arr[2] 在矩阵中的第一行或第二列上都被涂色。 +``` + + **示例 2:** + +![image explanation for example 2](https://assets.leetcode.com/uploads/2023/01/18/grid2.jpg) +``` +输入:arr = [2,8,7,4,1,3,5,6,9], mat = [[3,2,5],[1,4,6],[8,7,9]] +输出:3 +解释:遍历如上图所示,arr[3] 在矩阵中的第二列上都被涂色。 +``` + + **提示:** + +* `m == mat.length` +* `n = mat[i].length` +* `arr.length == m * n` +* `1 <= m, n <= 10^5` +* `1 <= m * n <= 10^5` +* `1 <= arr[i], mat[r][c] <= m * n` +* `arr` 中的所有整数 **互不相同** +* `mat` 中的所有整数 **互不相同** + +--- + +哈希表: + +因为矩阵中的每个数都不一样,因此用哈希表记录每个数的坐标。接着遍历涂色的数组,从哈希表中取出当前值的坐标。用一个长度为 `m` 的数组记录每一行被涂色的个数,长度为 `n` 的数组记录每一列被涂色的个数。若当前行被涂色的个数为 `n` 或当前列被涂色的个数为 `m` 时,意味着已经涂满当前行或列。 + +```Java +class Solution { + public int firstCompleteIndex(int[] arr, int[][] mat) { + int m = mat.length, n = mat[0].length; + HashMap hashmap = new HashMap<>(); + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + hashmap.put(mat[i][j], new int[]{i, j}); + } + } + int[] rowCount = new int[m], columnCount = new int[n]; + for(int i = 0; i < m * n; i++){ + int[] temp = hashmap.get(arr[i]); + if(++rowCount[temp[0]] == n || ++columnCount[temp[1]] == m){ + return i; + } + } + return -1; + } +} +``` \ No newline at end of file diff --git "a/Hash Table/299. \347\214\234\346\225\260\345\255\227\346\270\270\346\210\217.md" "b/Hash Table/299. \347\214\234\346\225\260\345\255\227\346\270\270\346\210\217.md" new file mode 100644 index 0000000..b5641c7 --- /dev/null +++ "b/Hash Table/299. \347\214\234\346\225\260\345\255\227\346\270\270\346\210\217.md" @@ -0,0 +1,75 @@ +#### 299. 猜数字游戏 + +难度:中等 + +--- + +你在和朋友一起玩 [猜数字(Bulls and Cows)](https://baike.baidu.com/item/%E7%8C%9C%E6%95%B0%E5%AD%97/83200?fromtitle=Bulls+and+Cows&fromid=12003488&fr=aladdin)游戏,该游戏规则如下: + +写出一个秘密数字,并请朋友猜这个数字是多少。朋友每猜测一次,你就会给他一个包含下述信息的提示: + +* 猜测数字中有多少位属于数字和确切位置都猜对了(称为 "Bulls",公牛), +* 有多少位属于数字猜对了但是位置不对(称为 "Cows",奶牛)。也就是说,这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字。 + +给你一个秘密数字 `secret` 和朋友猜测的数字 `guess` ,请你返回对朋友这次猜测的提示。 + +提示的格式为 `"xAyB"` ,`x` 是公牛个数, `y` 是奶牛个数,`A` 表示公牛,`B` 表示奶牛。 + +请注意秘密数字和朋友猜测的数字都可能含有重复数字。 + + **示例 1:** + +``` +输入:secret = "1807", guess = "7810" +输出:"1A3B" +解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。 +"1807" + | +"7810" +``` + + **示例 2:** + +``` +输入:secret = "1123", guess = "0111" +输出:"1A1B" +解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。 +"1123" "1123" + | or | +"0111" "0111" +注意,两个不匹配的 1 中,只有一个会算作奶牛(数字猜对位置不对)。通过重新排列非公牛数字,其中仅有一个 1 可以成为公牛数字。 +``` + + **提示:** + +* `1 <= secret.length, guess.length <= 1000` +* `secret.length == guess.length` +* `secret` 和 `guess` 仅由数字组成 + +--- + +哈希表: + +- 当前位置的字符不一致时,使用两个哈希表各自记录当前字符出现的次数,最后再比较两个哈希表中共同元素的最小值,。 + +- 当前位置的字符一致时,即为公牛的个数, + +```go +func getHint(secret string, guess string) string { + sz := len(secret) + var countB, countC int + arrayS, arrayG := make(map[byte]int), make(map[byte]int) + for i := 0; i < sz; i++ { + if secret[i] == guess[i] { + countB++ + } else { + arrayS[secret[i]]++ + arrayG[guess[i]]++ + } + } + for k, v := range arrayS { + countC += min(v, arrayG[k]) + } + return fmt.Sprintf("%dA%dB", countB, countC) +} +``` \ No newline at end of file diff --git "a/Hash Table/49. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" "b/Hash Table/49. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" new file mode 100644 index 0000000..ca85659 --- /dev/null +++ "b/Hash Table/49. \345\255\227\346\257\215\345\274\202\344\275\215\350\257\215\345\210\206\347\273\204.md" @@ -0,0 +1,70 @@ +#### 49. 字母异位词分组 + +难度:中等 + +--- + +给你一个字符串数组,请你将 **字母异位词** 组合在一起。可以按任意顺序返回结果列表。 + + **字母异位词** 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。 + + **示例 1:** + +``` +输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] +输出: [["bat"],["nat","tan"],["ate","eat","tea"]] +``` + + **示例 2:** + +``` +输入: strs = [""] +输出: [[""]] +``` + + **示例 3:** + +``` +输入: strs = ["a"] +输出: [["a"]] +``` + + **提示:** + +* `1 <= strs.length <= 10^4` +* `0 <= strs[i].length <= 100` +* `strs[i]` 仅包含小写字母 + +--- + +哈希: + +对于字符串数组中的每一个字符串,按其字典序排序(实现方式是用长度为26的数组记录),如果在哈希表中存在该排序后的字符串,则存入列表中。 + +```Java +class Solution { + public List> groupAnagrams(String[] strs) { + HashMap> hashmap = new HashMap<>(); + int n = strs.length; + for(int i = 0; i < n; i++){ + int[] alphabet = new int[26]; + for(int j = 0; j < strs[i].length(); j++){ + char c = strs[i].charAt(j); + alphabet[c - 'a'] ++; + } + StringBuilder sb = new StringBuilder(); + for(int j = 0; j < 26; j++){ + while(alphabet[j] != 0){ + sb.append(j + 'a'); + alphabet[j]--; + } + } + String key = sb.toString(); + List temp = hashmap.getOrDefault(key, new ArrayList()); + temp.add(strs[i]); + hashmap.put(key, temp); + } + return new ArrayList>(hashmap.values()); + } +} +``` \ No newline at end of file diff --git "a/Hash Table/560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Hash Table/560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" new file mode 100644 index 0000000..2b7b53b --- /dev/null +++ "b/Hash Table/560. \345\222\214\344\270\272 K \347\232\204\345\255\220\346\225\260\347\273\204.md" @@ -0,0 +1,51 @@ +#### 560. 和为 K 的子数组 + +难度:中等 + +--- + +给你一个整数数组 `nums` 和一个整数 `k` ,请你统计并返回 _该数组中和为 `k` 的连续子数组的个数_ 。 + + **示例 1:** + +``` +输入:nums = [1,1,1], k = 2 +输出:2 +``` + + **示例 2:** + +``` +输入:nums = [1,2,3], k = 3 +输出:2 +``` + + **提示:** + +* `1 <= nums.length <= 2 * 10^4` +* `-1000 <= nums[i] <= 1000` +* `-10^7 <= k <= 10^7` + +--- + +前缀和 + 哈希表: + +若用数组 `pre[n]` 记录 `[0,n]` 所有数的和,那么 `pre[i] = pre[i - 1] + nums[i]`,也就是数组 `pre[n]` 是一个记录前缀和的数组。若**连续子数组** `[j,...,i]` 的和为 `k`,那么可以得到 `pre[i] - pre[j - 1] = k`,即 `pre[j - 1] = pre[i] - k`。因此如果将数组替换成哈希表,即可在 `O(1)` 时间内查找到是否存在某个下标为 `j - 1`(当然,下标不重要),值为 `pre[i] - k` 的数。附上[参考链接](https://leetcode.cn/problems/subarray-sum-equals-k/solution/he-wei-kde-zi-shu-zu-by-leetcode-solution/)。 + +```Java +class Solution { + public int subarraySum(int[] nums, int k) { + int ans = 0, n = nums.length, sum = 0; + HashMap hashmap = new HashMap<>(); + hashmap.put(0, 1); + for(int i = 0; i < n; i++){ + sum += nums[i]; + if(hashmap.containsKey(sum - k)){ + ans += hashmap.get(sum - k); + } + hashmap.put(sum, hashmap.getOrDefault(sum, 0) + 1); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Heap/1046. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.md" "b/Heap/1046. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.md" new file mode 100644 index 0000000..9c6b164 --- /dev/null +++ "b/Heap/1046. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217.md" @@ -0,0 +1,82 @@ +#### 1046. 最后一块石头的重量 + +难度:简单 + +--- + +有一堆石头,每块石头的重量都是正整数。 + +每一回合,从中选出两块 **最重的** 石头,然后将它们一起粉碎。假设石头的重量分别为 `x` 和 `y`,且 `x <= y`。那么粉碎的可能结果如下: + +* 如果 `x == y`,那么两块石头都会被完全粉碎; +* 如果 `x != y`,那么重量为 `x` 的石头将会完全粉碎,而重量为 `y` 的石头新重量为 `y-x`。 + +最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 `0`。 + + **示例:** + +``` +输入:[2,7,4,1,8,1] +输出:1 +解释: +先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1], +再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1], +接着是 2 和 1,得到 1,所以数组转换为 [1,1,1], +最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。 +``` + + **提示:** + +* `1 <= stones.length <= 30` +* `1 <= stones[i] <= 1000` + +--- + +最大堆: + +每次连续两次弹出堆顶元素并相减,再将相减后的值插入到堆中。 + +```Go +type hp struct { + sort.IntSlice +} + +func (h hp) Less(i, j int) bool { + return h.IntSlice[i] > h.IntSlice[j] +} + +func (h *hp) Push(v interface{}) { + h.IntSlice = append(h.IntSlice, v.(int)) +} + +func (h *hp) Pop() interface{} { + temp := h.IntSlice + v := temp[len(temp) - 1] + h.IntSlice = temp[:len(temp) - 1] + return v +} + +func (h *hp) push(v int) { + heap.Push(h, v) +} + +func (h *hp) pop() int { + return heap.Pop(h).(int) +} + +func lastStoneWeight(stones []int) int { + maxnHeap := &hp{stones} + heap.Init(maxnHeap) + for len(maxnHeap.IntSlice) > 1 { + y := maxnHeap.pop() + x := maxnHeap.pop() + if y - x != 0 { + maxnHeap.push(y - x) + } + } + if len(maxnHeap.IntSlice) == 0 { + return 0 + } + return maxnHeap.pop() +} +``` \ No newline at end of file diff --git "a/Linked List/114. \344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.md" "b/Linked List/114. \344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.md" new file mode 100644 index 0000000..e740ee9 --- /dev/null +++ "b/Linked List/114. \344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.md" @@ -0,0 +1,83 @@ +#### 114. 二叉树展开为链表 + +难度:中等 + +--- + +给你二叉树的根结点 `root` ,请你将它展开为一个单链表: + +* 展开后的单链表应该同样使用 `TreeNode` ,其中 `right` 子指针指向链表中下一个结点,而左子指针始终为 `null` 。 +* 展开后的单链表应该与二叉树 [ **先序遍历** ](https://baike.baidu.com/item/%E5%85%88%E5%BA%8F%E9%81%8D%E5%8E%86/6442839?fr=aladdin) 顺序相同。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/01/14/flaten.jpg) +``` +输入: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)` 额外空间)展开这棵树吗? + +--- + +链表的先序遍历:主要分为以下几步 + +1. 左子树插入到右子树 +2. 将原来的右子树接到左子树的最右边节点 +3. 考虑新的右子树的根节点,重复步骤 1,直到新的右子树为 null + +```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) { + while(root != null){ + if(root.left == null){ + root = root.right; + } else { + TreeNode temp = root.left; + while(temp.right != null){ + temp = temp.right; + } + temp.right = root.right; + root.right = root.left; + root.left = null; + root = root.right; + } + } + } +} +``` \ No newline at end of file diff --git "a/Linked List/141. \347\216\257\345\275\242\351\223\276\350\241\250.md" "b/Linked List/141. \347\216\257\345\275\242\351\223\276\350\241\250.md" new file mode 100644 index 0000000..c11f9dd --- /dev/null +++ "b/Linked List/141. \347\216\257\345\275\242\351\223\276\350\241\250.md" @@ -0,0 +1,83 @@ +#### 141. 环形链表 + +难度:简单 + +--- + +给你一个链表的头节点 `head` ,判断链表中是否有环。 + +如果链表中有某个节点,可以通过连续跟踪 `next` 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 **注意:`pos` 不作为参数进行传递**  。仅仅是为了标识链表的实际情况。 + +_如果链表中存在环_ ,则返回 `true` 。 否则,返回 `false` 。 + + **示例 1:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist.png) + +``` +输入:head = [3,2,0,-4], pos = 1 +输出:true +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + + **示例 2:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) + +``` +输入:head = [1,2], pos = 0 +输出:true +解释:链表中有一个环,其尾部连接到第一个节点。 +``` + + **示例 3:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test3.png) + +``` +输入:head = [1], pos = -1 +输出:false +解释:链表中没有环。 +``` + + **提示:** + +* 链表中节点的数目范围是 `[0, 10^4]` +* `-10^5 <= Node.val <= 10^5` +* `pos` 为 `-1` 或者链表中的一个 **有效索引** 。 + + **进阶:** 你能用 `O(1)`(即,常量)内存解决此问题吗? + +--- + +快慢指针: + +慢指针每次移动一个节点,快指针每次移动两个节点。 + +快指针总是先于慢指针的遍历。因此在循环中只需要判断快指针及其下一节点是否为空即可,否则如果存在环,快指针总会遇到慢指针。 + +```Java +/** + * Definition for singly-linked list. + * class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { + * val = x; + * next = null; + * } + * } + */ +public class Solution { + public boolean hasCycle(ListNode head) { + if(head == null || head.next == null) return false; + ListNode slow = head, fast = head.next; + while(slow != fast){ + if(fast == null || fast.next == null) return false; + slow = slow.next; + fast = fast.next.next; + } + return true; + } +} +``` \ No newline at end of file diff --git "a/Linked List/142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" "b/Linked List/142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" new file mode 100644 index 0000000..93ce2c1 --- /dev/null +++ "b/Linked List/142. \347\216\257\345\275\242\351\223\276\350\241\250 II.md" @@ -0,0 +1,99 @@ +#### 142. 环形链表 II + +难度:中等 + +--- + +给定一个链表的头节点  `head` ,返回链表开始入环的第一个节点。 _如果链表无环,则返回 `null`。_ + +如果链表中有某个节点,可以通过连续跟踪 `next` 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 `pos` 来表示链表尾连接到链表中的位置( **索引从 0 开始** )。如果 `pos` 是 `-1`,则在该链表中没有环。 **注意:`pos` 不作为参数进行传递** ,仅仅是为了标识链表的实际情况。 + + **不允许修改** 链表。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) + +``` +输入:head = [3,2,0,-4], pos = 1 +输出:返回索引为 1 的链表节点 +解释:链表中有一个环,其尾部连接到第二个节点。 +``` + + **示例 2:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test2.png) + +``` +输入:head = [1,2], pos = 0 +输出:返回索引为 0 的链表节点 +解释:链表中有一个环,其尾部连接到第一个节点。 +``` + + **示例 3:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/07/circularlinkedlist_test3.png) + +``` +输入:head = [1], pos = -1 +输出:返回 null +解释:链表中没有环。 +``` + + **提示:** + +* 链表中节点的数目范围在范围 `[0, 10^4]` 内 +* `-10^5 <= Node.val <= 10^5` +* `pos` 的值为 `-1` 或者链表中的一个有效索引 + + **进阶:** 你是否可以使用 `O(1)` 空间解决此题? + +--- + +快慢指针: + +![](https://raw.githubusercontent.com/CompetitiveLin/ImageHostingService/picgo/imgs/202310232256747.png) + +快指针走过的路程永远是慢指针的两倍,根据这个性质得到以下公式: +$$ +a + n * (b + c) + b = 2 * (a + b) +$$ +化简后为 +$$ +a = (n - 1) * (b + c) + c +$$ +可以发现 $(b + c)$ 恰好为环的长度,那么意味着从链表头部到入环的距离($a$)恰好等于从相遇点到入环点的距离($c$)再加上 $n-1$ 圈个环的长度。**当快慢指针相遇后,再定义一个指针指向链表起点,一次走一步,`slow` 指针也同步继续往后走,那么这两个指针就一定会在链表的入口位置相遇。** + +```Java +/** + * Definition for singly-linked list. + * class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { + * val = x; + * next = null; + * } + * } + */ +public class Solution { + public ListNode detectCycle(ListNode head) { + if(head == null || head.next == null) return null; + ListNode slow = head, fast = head; + while(fast != null && fast.next != null){ + slow = slow.next; + fast = fast.next.next; + if(slow == fast){ + ListNode ans = head; + while(slow != ans){ + slow = slow.next; + ans = ans.next; + } + return ans; + } + + } + return null; + } +} +``` \ No newline at end of file diff --git "a/Linked List/206. \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/Linked List/206. \345\217\215\350\275\254\351\223\276\350\241\250.md" index c615f39..a79cb42 100644 --- "a/Linked List/206. \345\217\215\350\275\254\351\223\276\350\241\250.md" +++ "b/Linked List/206. \345\217\215\350\275\254\351\223\276\350\241\250.md" @@ -65,6 +65,32 @@ class Solution { } ``` +Go语言: + +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func reverseList(head *ListNode) *ListNode { + var pre *ListNode + for head != nil { + next := head.Next + head.Next = pre + pre = head + head = next + } + return pre +} +``` + + + + + 方法二,递归: ```java diff --git "a/Linked List/2095. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\350\212\202\347\202\271.md" "b/Linked List/2095. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\350\212\202\347\202\271.md" new file mode 100644 index 0000000..1d2ee31 --- /dev/null +++ "b/Linked List/2095. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\350\212\202\347\202\271.md" @@ -0,0 +1,86 @@ +#### 2095. 删除链表的中间节点 + +难度:中等 + +--- + +给你一个链表的头节点 `head` 。 **删除** 链表的 **中间节点** ,并返回修改后的链表的头节点 `head` 。 + +长度为 `n` 链表的中间节点是从头数起第 `⌊n / 2⌋` 个节点(下标从 **0** 开始),其中 `⌊x⌋` 表示小于或等于 `x` 的最大整数。 + +* 对于 `n` = `1`、`2`、`3`、`4` 和 `5` 的情况,中间节点的下标分别是 `0`、`1`、`1`、`2` 和 `2` 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/11/16/eg1drawio.png) + +``` +输入:head = [1,3,4,7,1,2,6] +输出:[1,3,4,1,2,6] +解释: +上图表示给出的链表。节点的下标分别标注在每个节点的下方。 +由于 n = 7 ,值为 7 的节点 3 是中间节点,用红色标注。 +返回结果为移除节点后的新链表。 +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/11/16/eg2drawio.png) + +``` +输入:head = [1,2,3,4] +输出:[1,2,4] +解释: +上图表示给出的链表。 +对于 n = 4 ,值为 3 的节点 2 是中间节点,用红色标注。 +``` + + **示例 3:** + +![](https://assets.leetcode.com/uploads/2021/11/16/eg3drawio.png) + +``` +输入:head = [2,1] +输出:[2] +解释: +上图表示给出的链表。 +对于 n = 2 ,值为 1 的节点 1 是中间节点,用红色标注。 +值为 2 的节点 0 是移除节点 1 后剩下的唯一一个节点。 +``` + + **提示:** + +* 链表中节点的数目在范围 `[1, 10^5]` 内 +* `1 <= Node.val <= 10^5` + +--- + +快慢指针: + +通过找规律发现,当快指针为空或者快指针的下一个节点为空时,慢指针恰好为需要被删除的节点,因此用 `pre` 节点记录被删除节点的前一个节点。需要额外考虑的情况为当链表只存在一个节点时,直接返回空。 + +```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 deleteMiddle(ListNode head) { + if(head.next == null) return null; + ListNode fast = head, slow = head, pre = null; + while(fast != null && fast.next != null){ + pre = slow; + slow = slow.next; + fast = fast.next.next; + } + pre.next = pre.next.next; + return head; + } +} +``` \ No newline at end of file diff --git "a/Linked List/234. \345\233\236\346\226\207\351\223\276\350\241\250.md" "b/Linked List/234. \345\233\236\346\226\207\351\223\276\350\241\250.md" new file mode 100644 index 0000000..be70d97 --- /dev/null +++ "b/Linked List/234. \345\233\236\346\226\207\351\223\276\350\241\250.md" @@ -0,0 +1,82 @@ +#### 234. 回文链表 + +难度:简单 + +--- + +给你一个单链表的头节点 `head` ,请你判断该链表是否为 + +回文链表 + +。如果是,返回 `true` ;否则,返回 `false` 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/03/03/pal1linked-list.jpg) +``` +输入:head = [1,2,2,1] +输出:true +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/03/03/pal2linked-list.jpg) +``` +输入:head = [1,2] +输出:false +``` + + **提示:** + +* 链表中节点数目在范围`[1, 10^5]` 内 +* `0 <= Node.val <= 9` + + **进阶:** 你能否用 `O(n)` 时间复杂度和 `O(1)` 空间复杂度解决此题? + +--- + +进阶,用快慢指针和反转链表的方式: + +首先用快慢指针找到链表中点,接着将中点后的节点反转,得到新的节点后与 `head` 节点逐个比较值。 + +```Go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func isPalindrome(head *ListNode) bool { + mid := endOfFirstHalf(head) + end := reverse(mid.Next) + for end != nil { + if head.Val != end.Val { + return false + } + end = end.Next + head = head.Next + } + return true +} + +func reverse(cur *ListNode) *ListNode { + var pre *ListNode + for cur != nil { + next := cur.Next + cur.Next = pre + pre = cur + cur = next + } + return pre +} + +func endOfFirstHalf(head *ListNode) *ListNode { + slow, fast := head, head + for fast.Next != nil && fast.Next.Next != nil { + slow = slow.Next + fast = fast.Next.Next + } + return slow +} +``` \ No newline at end of file diff --git "a/Linked List/235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Linked List/235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" new file mode 100644 index 0000000..fc7ee02 --- /dev/null +++ "b/Linked List/235. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" @@ -0,0 +1,61 @@ +#### 235. 二叉搜索树的最近公共祖先 + +难度:中等 + +--- + +给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 + +[百度百科](https://baike.baidu.com/item/%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88/8918834?fr=aladdin)中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大( **一个节点也可以是它自己的祖先** )。” + +例如,给定如下二叉搜索树:  root = \[6,2,8,0,4,7,9,null,null,3,5\] + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/binarysearchtree_improved.png) + + **示例 1:** + +``` +输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 +输出: 6 +解释: 节点 2 和节点 8 的最近公共祖先是 6。 +``` + + **示例 2:** + +``` +输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 +输出: 2 +解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。 +``` + + **说明:** + +* 所有节点的值都是唯一的。 +* p、q 为不同节点且均存在于给定的二叉搜索树中。 + +--- + +迭代遍历: + +根据二叉搜索树的特点,左子树的值比根节点小,左子树的值比根节点大。每次都将根节点和给定的 `p, q` 两个节点的值进行比较,再将根节点的左子树或右子树赋值给根节点,直到根节点的值在 `p` 和 `q` 节点值之间。因为不确定 `p` 还是 `q` 节点值的大小,所以使用根节点和两个节点差的乘积是否小于零判断。 + +```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) { + while((long)(root.val - p.val) * (long)(root.val - q.val) > 0){ + root = root.val < p.val ? root.right : root.left; + } + return root; + } +} +``` \ No newline at end of file diff --git "a/Linked List/236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/Linked List/236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" new file mode 100644 index 0000000..68cb3df --- /dev/null +++ "b/Linked List/236. \344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.md" @@ -0,0 +1,73 @@ +#### 236. 二叉树的最近公共祖先 + +难度:中等 + +--- + +给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + +[百度百科](https://baike.baidu.com/item/%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88/8918834?fr=aladdin)中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大( **一个节点也可以是它自己的祖先** )。” + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) +``` +输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +输出:3 +解释:节点 5 和节点 1 的最近公共祖先是节点 3 。 +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2018/12/14/binarytree.png) +``` +输入: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, 10^5]` 内。 +* `-10^9 <= Node.val <= 10^9` +* 所有 `Node.val` `互不相同` 。 +* `p != q` +* `p` 和 `q` 均存在于给定的二叉树中。 + +--- + +深度优先搜索: + +核心在于根据返回空节点还是非空节点判断是否找到了 `p` 或 `q` 节点。这道题得靠背诵。。 + +```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) { + if(root == null || root == p || root == q){ + return root; + } + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + if(left != null && right != null){ + return root; + } + return left != null ? left : right; + } +} +``` \ No newline at end of file diff --git "a/Linked List/25. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" "b/Linked List/25. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" new file mode 100644 index 0000000..4dca00f --- /dev/null +++ "b/Linked List/25. K \344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.md" @@ -0,0 +1,77 @@ +#### 25. K 个一组翻转链表 + +难度:困难 + +--- + +给你链表的头节点 `head` ,每 `k` 个节点一组进行翻转,请你返回修改后的链表。 + +`k` 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 `k` 的整数倍,那么请将最后剩余的节点保持原有顺序。 + +你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex1.jpg) +``` +输入:head = [1,2,3,4,5], k = 2 +输出:[2,1,4,3,5] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex2.jpg) + +``` +输入:head = [1,2,3,4,5], k = 3 +输出:[3,2,1,4,5] +``` + + **提示:** + +* 链表中的节点数目为 `n` +* `1 <= k <= n <= 5000` +* `0 <= Node.val <= 1000` + + **进阶:** 你可以设计一个只用 `O(1)` 额外内存空间的算法解决此问题吗? + +--- + +结合 [92. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/),遍历 `k` 个节点后,原地反转链表。 + +![image.png](https://pic.leetcode-cn.com/1615105296-bmiPxl-image.png) + +```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(-1, head), pre = dummy; + while(true){ + ListNode tail = pre; + for(int i = 0; i < k; i++){ + tail = tail.next; + if(tail == null){ + return dummy.next; + } + } + ListNode cur = pre.next, next; + for(int i = 0; i < k - 1; i++){ + next = cur.next; + cur.next = next.next; + next.next = pre.next; + pre.next = next; + } + pre = cur; + } + } +} +``` \ No newline at end of file diff --git "a/Linked List/328. \345\245\207\345\201\266\351\223\276\350\241\250.md" "b/Linked List/328. \345\245\207\345\201\266\351\223\276\350\241\250.md" new file mode 100644 index 0000000..493d7b4 --- /dev/null +++ "b/Linked List/328. \345\245\207\345\201\266\351\223\276\350\241\250.md" @@ -0,0 +1,72 @@ +#### 328. 奇偶链表 + +难度:中等 + +--- + +给定单链表的头节点 `head` ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。 + + **第一个** 节点的索引被认为是 **奇数** , **第二个** 节点的索引为  **偶数** ,以此类推。 + +请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。 + +你必须在 `O(1)` 的额外空间复杂度和 `O(n)` 的时间复杂度下解决这个问题。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/03/10/oddeven-linked-list.jpg) + +``` +输入: head = [1,2,3,4,5] +输出: [1,3,5,2,4] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/03/10/oddeven2-linked-list.jpg) + +``` +输入: head = [2,1,3,5,6,4,7] +输出: [2,3,6,7,1,5,4] +``` + + **提示:** + +* `n ==`  链表中的节点数 +* `0 <= n <= 10^4` +* `-10^6 <= Node.val <= 10^6` + +--- + +双指针: + +一个指针指向头节点(代表奇数节点),另一个指针指向头节点的下一个节点(代表偶数节点),接着奇数节点和偶数节点的下一个节点均指向原节点下一个节点的下一个节点(完成节点的跨越)。 + +因为偶数节点肯定位于奇数节点后,那么循环推出条件只需要判断偶数节点和偶数节点的下一个节点是否为空(通过多次尝试得出的结论),最后退出循环时将两个节点拼接到一起即可。 + +```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 oddEvenList(ListNode head) { + if(head == null || head.next == null) return head; + ListNode odd = head, even = head.next, temp = even; + while(even != null && even.next != null){ + odd.next = odd.next.next; + even.next = even.next.next; + odd = odd.next; + even = even.next; + } + odd.next = temp; + return head; + } +} +``` \ No newline at end of file diff --git "a/Linked List/445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" "b/Linked List/445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" new file mode 100644 index 0000000..8091acc --- /dev/null +++ "b/Linked List/445. \344\270\244\346\225\260\347\233\270\345\212\240 II.md" @@ -0,0 +1,89 @@ +#### 445. 两数相加 II + +难度:中等 + +--- + +给你两个 **非空** 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 + +你可以假设除了数字 0 之外,这两个数字都不会以零开头。 + + **示例1:** + +![](https://pic.leetcode-cn.com/1626420025-fZfzMX-image.png) + +``` +输入:l1 = [7,2,4,3], l2 = [5,6,4] +输出:[7,8,0,7] +``` + + **示例2:** + +``` +输入:l1 = [2,4,3], l2 = [5,6,4] +输出:[8,0,7] +``` + + **示例3:** + +``` +输入:l1 = [0], l2 = [0] +输出:[0] +``` + + **提示:** + +* 链表的长度范围为 `[1, 100]` +* `0 <= node.val <= 9` +* 输入数据保证链表代表的数字无前导 0 + + **进阶:** 如果输入链表不能翻转该如何解决? + +--- + +反转链表 + 两数相加: + +思路比较简单,**先反转两个链表,各个位上的数相加后再将链表反转**。中间的一些细节,比如当前两个链表指针都为空但仍有进位时,不能忽略这种情况。 + +```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 addTwoNumbers(ListNode l1, ListNode l2) { + ListNode head1 = reverseListNode(l1), head2 = reverseListNode(l2), dummy = new ListNode(0), cur = dummy; + int carry = 0; + while(head1 != null || head2 != null || carry != 0){ + if(head1 != null){ + carry += head1.val; + head1 = head1.next; + } + if(head2 != null) { + carry += head2.val; + head2 = head2.next; + } + cur = cur.next = new ListNode(carry % 10); // Java 连等赋值时自右向左赋值的 + carry /= 10; + } + return reverseListNode(dummy.next); + } + + public ListNode reverseListNode(ListNode head){ + ListNode pre = null, node = head; + while(node != null){ + ListNode temp = node.next; + node.next = pre; + pre = node; + node = temp; + } + return pre; + } +} +``` \ No newline at end of file diff --git "a/Linked List/450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" "b/Linked List/450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" new file mode 100644 index 0000000..003a8a8 --- /dev/null +++ "b/Linked List/450. \345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.md" @@ -0,0 +1,118 @@ +#### 450. 删除二叉搜索树中的节点 + +难度:中等 + +--- + +给定一个二叉搜索树的根节点 **root** 和一个值 **key** ,删除二叉搜索树中的  **key**  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 + +一般来说,删除节点可分为两个步骤: + +1. 首先找到需要删除的节点; +2. 如果找到了,删除它。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2020/09/04/del_node_1.jpg) + +``` +输入:root = [5,3,6,2,4,null,7], key = 3 +输出:[5,4,6,2,null,null,7] +解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 +一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。 +另一个正确答案是 [5,2,6,null,4,null,7]。 +``` + + **示例 2:** + +``` +输入: root = [5,3,6,2,4,null,7], key = 0 +输出: [5,3,6,2,4,null,7] +解释: 二叉树不包含值为 0 的节点 +``` + + **示例 3:** + +``` +输入: root = [], key = 0 +输出: [] +``` + + **提示:** + +* 节点数的范围 `[0, 10^4]`. +* `-10^5 <= Node.val <= 10^5` +* 节点值唯一 +* `root` 是合法的二叉搜索树 +* `-10^5 <= key <= 10^5` + + **进阶:** 要求算法时间复杂度为 O(h),h 为树的高度。 + +--- + +链表的删除: + +将需要删除的节点的左节点连接至右节点的最左节点。 + +```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 TreeNode deleteNode(TreeNode root, int key) { + TreeNode node = root, dummy = new TreeNode(), pre = dummy; + dummy.left = root; + boolean isLeft = true; + while(node !=null && node.val != key){ + pre = node; + if(node.val < key){ + node = node.right; + isLeft = false; + } + else { + node = node.left; + isLeft = true; + } + } + if(node == null) return root; + jointNode(pre, isLeft); + return dummy.left; + } + + private void jointNode(TreeNode pre, boolean isLeft){ + TreeNode left = null, right = null; + if(isLeft) { + left = pre.left.left; + right = pre.left.right; + } else { + left = pre.right.left; + right = pre.right.right; + } + if(right == null){ + if(isLeft) pre.left = left; + else pre.right = left; + } else { + TreeNode temp = right; + while(temp.left != null){ + temp = temp.left; + } + temp.left = left; + if(isLeft) pre.left = right; + else pre.right = right; + } + + } +} +``` \ No newline at end of file diff --git "a/Linked List/82. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" "b/Linked List/82. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" new file mode 100644 index 0000000..9fc5d5b --- /dev/null +++ "b/Linked List/82. \345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240 II.md" @@ -0,0 +1,65 @@ +#### 82. 删除排序链表中的重复元素 II + +难度:中等 + +--- + +给定一个已排序的链表的头 `head` , _删除原始链表中所有重复数字的节点,只留下不同的数字_ 。返回 _已排序的链表_ 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/01/04/linkedlist1.jpg) +``` +输入:head = [1,2,3,3,4,4,5] +输出:[1,2,5] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2021/01/04/linkedlist2.jpg) +``` +输入:head = [1,1,1,2,3] +输出:[2,3] +``` + + **提示:** + +* 链表中节点数目在范围 `[0, 300]` 内 +* `-100 <= Node.val <= 100` +* 题目数据保证链表已经按升序 **排列** + +--- + +用两个节点分别表示 **当前节点** 和 **当前节点的前一个节点**。 + +如果当前节点值等于下一个节点值,一直遍历至不等为止,再判断当前节点是否移动过(通过前一个节点的下一个节点是否是当前节点),如果移动过,则两个节点同时移动,否则,前一个节点的下一个节点指向当前节点的下一个节点。 + +另外,当头节点有可能发送变化时,此时需要一个 `dummy` 节点指向头节点,最后再返回 `dummy.next`。 + +```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 deleteDuplicates(ListNode head) { + ListNode pre = new ListNode(-1, head), dummy = pre, cur = head; + while(cur != null){ + while(cur.next != null && cur.val == cur.next.val) cur = cur.next; + if(pre.next == cur){ + pre = pre.next; + } else { + pre.next = cur.next; + } + cur = cur.next; + } + return dummy.next; + } +} +``` \ No newline at end of file diff --git "a/Linked List/92. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" "b/Linked List/92. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" new file mode 100644 index 0000000..3356e2b --- /dev/null +++ "b/Linked List/92. \345\217\215\350\275\254\351\223\276\350\241\250 II.md" @@ -0,0 +1,148 @@ +#### 92. 反转链表 II + +难度:中等 + +--- + +给你单链表的头指针 `head` 和两个整数 `left` 和 `right` ,其中 `left <= right` 。请你反转从位置 `left` 到位置 `right` 的链表节点,返回 **反转后的链表** 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/02/19/rev2ex2.jpg) +``` +输入:head = [1,2,3,4,5], left = 2, right = 4 +输出:[1,4,3,2,5] +``` + + **示例 2:** + +``` +输入:head = [5], left = 1, right = 1 +输出:[5] +``` + + **提示:** + +* 链表中节点数目为 `n` +* `1 <= n <= 500` +* `-500 <= Node.val <= 500` +* `1 <= left <= right <= n` + + **进阶:** 你可以使用一趟扫描完成反转吗? + +--- + +法一:确定反转的窗口后再使用最普通的反转链表的方法。 + +```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 reverseBetween(ListNode head, int left, int right) { + + ListNode dummy = new ListNode(-1), pre = dummy; + dummy.next = head; + for(int i = 0; i < left - 1; i++){ + pre = pre.next; + } + ListNode rightNode = pre; + for(int i = 0; i < right - left + 1; i++){ + rightNode = rightNode.next; + } + ListNode leftNode = pre.next; + pre.next = null; + ListNode post = rightNode.next; + rightNode.next = null; + + reverse(leftNode); + + pre.next = rightNode; + leftNode.next = post; + return dummy.next; + } + + public void reverse(ListNode head){ + ListNode pre = null; + while(head != null){ + ListNode nextNode = head.next; + head.next = pre; + pre = head; + head = nextNode; + } + } + +} +``` + +--- + +法二:一遍遍历,固定 `pre` 节点,移动 `right - left` 次 `curr` 和 `next` 节点。 + +![image.png](https://pic.leetcode-cn.com/1615105296-bmiPxl-image.png) + +```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 reverseBetween(ListNode head, int left, int right) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode pre = dummy; + for(int i = 0; i < left - 1; i++){ + pre = pre.next; + } + ListNode cur = pre.next, next; + for(int i = 0; i < right - left; i++){ + next = cur.next; + cur.next = next.next; + next.next = pre.next; + pre.next = next; + } + return dummy.next; + } +} +``` + +Go语言: + +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func reverseBetween(head *ListNode, left int, right int) *ListNode { + dummy := &ListNode{-1, head} + pre := dummy + for i := 0; i < left - 1; i++ { + pre = pre.Next + } + cur, next := pre.Next, pre.Next.Next + for i := 0; i < right - left; i++ { + cur.Next = next.Next + next.Next = pre.Next + pre.Next = next + next = cur.Next + } + return dummy.Next +} +``` + diff --git "a/Linked List/94. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" "b/Linked List/94. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" new file mode 100644 index 0000000..b9128f2 --- /dev/null +++ "b/Linked List/94. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.md" @@ -0,0 +1,117 @@ +#### 94. 二叉树的中序遍历 + +难度:简单 + +--- + +给定一个二叉树的根节点 `root` ,返回 _它的 **中序**  遍历_ 。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) +``` +输入:root = [1,null,2,3] +输出:[1,3,2] +``` + + **示例 2:** + +``` +输入:root = [] +输出:[] +``` + + **示例 3:** + +``` +输入:root = [1] +输出:[1] +``` + + **提示:** + +* 树中节点数目在范围 `[0, 100]` 内 +* `-100 <= Node.val <= 100` + + **进阶:**  递归算法很简单,你可以通过迭代算法完成吗? + +--- + +递归: + +`res.add(root.val);` 若位于 `dfs(root.left, res)` 上方,是前序遍历;若位于下方,则是后序遍历。 + +```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 List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + dfs(root, res); + return res; + } + + private void dfs(TreeNode root, List res){ + if(root == null){ + return; + } + dfs(root.left, res); + res.add(root.val); + dfs(root.right, res); + } +} +``` + + + +迭代: + +借助栈 + +```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 List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + ArrayDeque stack = new ArrayDeque<>(); + while(root != null || !stack.isEmpty()){ + while(root != null){ + stack.push(root); + root = root.left; + } + root = stack.pop(); + res.add(root.val); + root = root.right; + } + return res; + } +} +``` + diff --git "a/Methodology/1010. \346\200\273\346\214\201\347\273\255\346\227\266\351\227\264\345\217\257\350\242\253 60 \346\225\264\351\231\244\347\232\204\346\255\214\346\233\262.md" "b/Methodology/1010. \346\200\273\346\214\201\347\273\255\346\227\266\351\227\264\345\217\257\350\242\253 60 \346\225\264\351\231\244\347\232\204\346\255\214\346\233\262.md" new file mode 100644 index 0000000..0710609 --- /dev/null +++ "b/Methodology/1010. \346\200\273\346\214\201\347\273\255\346\227\266\351\227\264\345\217\257\350\242\253 60 \346\225\264\351\231\244\347\232\204\346\255\214\346\233\262.md" @@ -0,0 +1,59 @@ +#### 1010. 总持续时间可被 60 整除的歌曲 + +难度:中等 + +--- + +在歌曲列表中,第 `i` 首歌曲的持续时间为 `time[i]` 秒。 + +返回其总持续时间(以秒为单位)可被 `60` 整除的歌曲对的数量。形式上,我们希望下标数字 `i` 和 `j` 满足  `i < j` 且有 `(time[i] + time[j]) % 60 == 0`。 + + **示例 1:** + +``` +输入:time = [30,20,150,100,40] +输出:3 +解释:这三对的总持续时间可被 60 整除: +(time[0] = 30, time[2] = 150): 总持续时间 180 +(time[1] = 20, time[3] = 100): 总持续时间 120 +(time[1] = 20, time[4] = 40): 总持续时间 60 +``` + + **示例 2:** + +``` +输入:time = [60,60,60] +输出:3 +解释:所有三对的总持续时间都是 120,可以被 60 整除。 +``` + + **提示:** + +* `1 <= time.length <= 6 * 10^4` +* `1 <= time[i] <= 500` + +--- + +组合数学: + +先取余,后求歌曲对的数量,例如 `i` 与 `60-i` 成一对。 + +```Java +class Solution { + public int numPairsDivisibleBy60(int[] time) { + int[] temp = new int[60]; + for(int i = 0; i < time.length; i++){ + temp[time[i] % 60]++; + } + long ans = 0; + for(int i = 0; i <= 30; i++){ + if(i == 0 || i == 30){ + ans += (long) temp[i] * (temp[i] - 1) / 2; + } else { + ans += (long) temp[i] * temp[60 - i]; + } + } + return (int)ans; + } +} +``` \ No newline at end of file diff --git "a/Methodology/1094. \346\213\274\350\275\246.md" "b/Methodology/1094. \346\213\274\350\275\246.md" new file mode 100644 index 0000000..d59f26b --- /dev/null +++ "b/Methodology/1094. \346\213\274\350\275\246.md" @@ -0,0 +1,61 @@ +#### 1094. 拼车 + +难度:中等 + +--- + +车上最初有 `capacity` 个空座位。车  **只能**  向一个方向行驶(也就是说, **不允许掉头或改变方向** ) + +给定整数 `capacity` 和一个数组 `trips` ,  `trip[i] = [numPassengersi, fromi, toi]` 表示第 `i` 次旅行有 `numPassengersi` 乘客,接他们和放他们的位置分别是 `fromi` 和 `toi` 。这些位置是从汽车的初始位置向东的公里数。 + +当且仅当你可以在所有给定的行程中接送所有乘客时,返回 `true`,否则请返回 `false`。 + + **示例 1:** + +``` +输入:trips = [[2,1,5],[3,3,7]], capacity = 4 +输出:false +``` + + **示例 2:** + +``` +输入:trips = [[2,1,5],[3,3,7]], capacity = 5 +输出:true +``` + + **提示:** + +* `1 <= trips.length <= 1000` +* `trips[i].length == 3` +* `1 <= numPassengersi <= 100` +* `0 <= fromi < toi <= 1000` +* `1 <= capacity <= 10^5` + +--- + +差分数组: + +初始化一个长度为 `1001` 的差分数组,遍历 `trips` 数组并确定最大长度,上车位置处增加 `numPassengers_i` ,下车位置处减少 `numPassengers_i`,再遍历一遍该差分数组,遍历的过程中如果数组和大于 `capacity`,证明超载,返回 `false`,否则遍历完成后返回` true`。 + +```Java +class Solution { + public boolean carPooling(int[][] trips, int capacity) { + int maxLength = 1001; + int[] arrays = new int[maxLength]; + for(int i = 0; i < trips.length; i++){ + arrays[trips[i][1]] += trips[i][0]; + arrays[trips[i][2]] -= trips[i][0]; + maxLength = Math.max(maxLength, trips[i][2]); + } + int count = 0; + for(int i = 0; i < maxLength; i++){ + count += arrays[i]; + if(count > capacity){ + return false; + } + } + return true; + } +} +``` \ No newline at end of file diff --git "a/Methodology/135. \345\210\206\345\217\221\347\263\226\346\236\234.md" "b/Methodology/135. \345\210\206\345\217\221\347\263\226\346\236\234.md" new file mode 100644 index 0000000..bb8e759 --- /dev/null +++ "b/Methodology/135. \345\210\206\345\217\221\347\263\226\346\236\234.md" @@ -0,0 +1,71 @@ +#### 135. 分发糖果 + +难度:困难 + +--- + +`n` 个孩子站成一排。给你一个整数数组 `ratings` 表示每个孩子的评分。 + +你需要按照以下要求,给这些孩子分发糖果: + +* 每个孩子至少分配到 `1` 个糖果。 +* 相邻两个孩子评分更高的孩子会获得更多的糖果。 + +请你给每个孩子分发糖果,计算并返回需要准备的 **最少糖果数目** 。 + + **示例 1:** + +``` +输入:ratings = [1,0,2] +输出:5 +解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。 +``` + + **示例 2:** + +``` +输入:ratings = [1,2,2] +输出:4 +解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 + 第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。 +``` + + **提示:** + +* `n == ratings.length` +* `1 <= n <= 2 * 10^4` +* `0 <= ratings[i] <= 2 * 10^4` + +--- + +两遍遍历: + +1. 第一次从左向右遍历,记录当前数只与相邻**左**数比较后应获得的糖果数 +2. 第二次从右向左遍历,记录当前数只与相邻**右**数比较后应获得的糖果数,两者取最大值。 + +根据题意,需要准备**最少**的糖果数,因此相邻数进行比较时,评分高的孩子的糖果数一定是比评分低的只多一颗糖果。 + +``` +func candy(ratings []int) int { + n := len(ratings) + candies := make([]int, n) + for i := range candies { + candies[i] = 1 + } + for i := 1; i < n; i++ { + if ratings[i] > ratings[i - 1] { + candies[i] = candies[i - 1] + 1 + } + } + for i := n - 2; i >= 0; i-- { + if ratings[i] > ratings[i + 1] { + candies[i] = max(candies[i], candies[i + 1] + 1) + } + } + res := 0 + for i := range candies { + res += candies[i] + } + return res +} +``` \ No newline at end of file diff --git "a/Methodology/1653. \344\275\277\345\255\227\347\254\246\344\270\262\345\271\263\350\241\241\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\254\241\346\225\260.md" "b/Methodology/1653. \344\275\277\345\255\227\347\254\246\344\270\262\345\271\263\350\241\241\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\254\241\346\225\260.md" new file mode 100644 index 0000000..b6df251 --- /dev/null +++ "b/Methodology/1653. \344\275\277\345\255\227\347\254\246\344\270\262\345\271\263\350\241\241\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\254\241\346\225\260.md" @@ -0,0 +1,60 @@ +#### 1653. 使字符串平衡的最少删除次数 + +难度:中等 + +--- + +给你一个字符串 `s` ,它仅包含字符 `'a'` 和 `'b'`​​​​ 。 + +你可以删除 `s` 中任意数目的字符,使得 `s` **平衡**  。当不存在下标对 `(i,j)` 满足 `i < j` ,且 `s[i] = 'b'` 的同时 `s[j]= 'a'` ,此时认为 `s` 是 **平衡** 的。 + +请你返回使 `s`  **平衡**  的 **最少**  删除次数。 + + **示例 1:** + +``` +输入:s = "aababbab" +输出:2 +解释:你可以选择以下任意一种方案: +下标从 0 开始,删除第 2 和第 6 个字符("aababbab" -> "aaabbb"), +下标从 0 开始,删除第 3 和第 6 个字符("aababbab" -> "aabbbb")。 +``` + + **示例 2:** + +``` +输入:s = "bbaaaaabb" +输出:2 +解释:唯一的最优解是删除最前面两个字符。 +``` + + **提示:** + +* `1 <= s.length <= 10^5` +* `s[i]` 要么是 `'a'` 要么是 `'b'`​ 。​ + +--- + +两次遍历: + +第一次从左向右遍历记录所有 `A` 的数量(换言之,右侧 `A` 的数量),第二次从左向右遍历根据不同情况增加左侧 `B` 的数量(当前字符为 `B`)或减少右侧 `A` 的数量(当前字符为 `A`),遍历过程中这两者数量和的最小值即是最后的答案。 + +类似于前缀和,也类似是求最长非递减子序列长度 + +```Java +class Solution { + public int minimumDeletions(String s) { + int rightA = 0, leftB = 0; + for(int i = 0; i < s.length(); i++){ + if(s.charAt(i) == 'a') rightA++; + } + int ans = rightA; + for(int i = 0; i < s.length(); i++){ + if(s.charAt(i) == 'a') rightA--; + else leftB++; + ans = Math.min(ans, leftB + rightA); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Methodology/189. \350\275\256\350\275\254\346\225\260\347\273\204.md" "b/Methodology/189. \350\275\256\350\275\254\346\225\260\347\273\204.md" new file mode 100644 index 0000000..bba72ff --- /dev/null +++ "b/Methodology/189. \350\275\256\350\275\254\346\225\260\347\273\204.md" @@ -0,0 +1,102 @@ +#### 189. 轮转数组 + +难度:中等 + +--- + +给定一个整数数组 `nums`,将数组中的元素向右轮转 `k` 个位置,其中 `k` 是非负数。 + + **示例 1:** + +``` +输入: nums = [1,2,3,4,5,6,7], k = 3 +输出: [5,6,7,1,2,3,4] +解释: +向右轮转 1 步: [7,1,2,3,4,5,6] +向右轮转 2 步: [6,7,1,2,3,4,5] +向右轮转 3 步: [5,6,7,1,2,3,4] +``` + + **示例 2:** + +``` +输入:nums = [-1,-100,3,99], k = 2 +输出:[3,99,-1,-100] +解释: +向右轮转 1 步: [99,-1,-100,3] +向右轮转 2 步: [3,99,-1,-100] +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `-2^31 <= nums[i] <= 2^31 - 1` +* `0 <= k <= 10^5` + + **进阶:** + +* 尽可能想出更多的解决方案,至少有 **三种** 不同的方法可以解决这个问题。 +* 你可以使用空间复杂度为 `O(1)` 的  **原地**  算法解决这个问题吗? + +--- + +技巧: + +**向右轮转 **的解决办法是先翻转整个数组,再各自反转 `[0, (k mod n) - 1]` 和 `[k mod n, n - 1]`。 + +另外,如果是 **向左轮转**,则是先反转整个数组,再各自反转 `[0, n - 1 - (k mod n)]` 和 `[n - (k mod n), n - 1]`。 + +```Java +// Java +class Solution { + public void rotate(int[] nums, int k) { + int n = nums.length; + k = k % n; + reverse(nums, 0, n - 1); + reverse(nums, 0, k - 1); + reverse(nums, k, n - 1); + } + + private void reverse(int[] nums, int left, int right){ + while(left < right){ + int temp = nums[left]; + nums[left] = nums[right]; + nums[right] = temp; + left++; + right--; + } + } +} +``` + +```go +// Go +func rotate(nums []int, k int) { + n := len(nums) + reverse(nums, 0, n - 1 - k) + reverse(nums, n - k, n - 1) + reverse(nums, 0, n - 1) +} + +func reverse(nums []int, left, right int) { + for ;left < right; { + nums[left], nums[right] = nums[right], nums[left] + left++ + right-- + } +} +``` + +方法二:额外数组 + +```go +func rotate(nums []int, k int) { + n := len(nums) + arr := make([]int, n) + k = k % n + for index := range nums { + arr[index] = nums[(n - k + index) % n] + } + copy(nums, arr) +} +``` \ No newline at end of file diff --git "a/Methodology/287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" "b/Methodology/287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" new file mode 100644 index 0000000..5b24ef8 --- /dev/null +++ "b/Methodology/287. \345\257\273\346\211\276\351\207\215\345\244\215\346\225\260.md" @@ -0,0 +1,67 @@ +#### 287. 寻找重复数 + +难度:中等 + +--- + +给定一个包含 `n + 1` 个整数的数组 `nums` ,其数字都在 `[1, n]` 范围内(包括 `1` 和 `n`),可知至少存在一个重复的整数。 + +假设 `nums` 只有 **一个重复的整数** ,返回  **这个重复的数** 。 + +你设计的解决方案必须 **不修改** 数组 `nums` 且只用常量级 `O(1)` 的额外空间。 + + **示例 1:** + +``` +输入:nums = [1,3,4,2,2] +输出:2 +``` + + **示例 2:** + +``` +输入:nums = [3,1,3,4,2] +输出:3 +``` + + **提示:** + +* `1 <= n <= 10^5` +* `nums.length == n + 1` +* `1 <= nums[i] <= n` +* `nums` 中 **只有一个整数** 出现 **两次或多次** ,其余整数均只出现 **一次** + +**进阶:** + +* 如何证明 `nums` 中至少存在一个重复的数字? +* 你可以设计一个线性级时间复杂度 `O(n)` 的解决方案吗? + +--- + +类似于环形链表找环的入口: + +以 `[1, 3, 4, 2, 2]` 为例,由下标对应到值有一层映射关系,即 `0 -> 1, 1 -> 3, 2 ->4, 3 -> 2, 4 -> 2`,将其串成一串得到下图: + + + +![](https://pic.leetcode-cn.com/999e055b41e499d9ac704abada4a1b8e6697374fdfedc17d06b0e8aa10a8f8f6-287.png) + +再利用快慢指针遍历该链表,可以得到快慢指针交界处,再初始化第三个指针为头位置,该指针与交界处的指针移动同样的距离即可得到环形链表入口处。 + +```Java +class Solution { + public int findDuplicate(int[] nums) { + int slow = 0, fast = 0; + do{ + slow = nums[slow]; + fast = nums[nums[fast]]; + }while(slow != fast); + int cur = 0; + while(cur != slow){ + cur = nums[cur]; + slow = nums[slow]; + } + return cur; + } +} +``` \ No newline at end of file diff --git "a/Methodology/31. \344\270\213\344\270\200\344\270\252\346\216\222\345\210\227.md" "b/Methodology/31. \344\270\213\344\270\200\344\270\252\346\216\222\345\210\227.md" new file mode 100644 index 0000000..a00c778 --- /dev/null +++ "b/Methodology/31. \344\270\213\344\270\200\344\270\252\346\216\222\345\210\227.md" @@ -0,0 +1,113 @@ +#### 31. 下一个排列 + +难度:中等 + +--- + +整数数组的一个 **排列**   就是将其所有成员以序列或线性顺序排列。 + +* 例如,`arr = [1,2,3]` ,以下这些都可以视作 `arr` 的排列:`[1,2,3]`、`[1,3,2]`、`[3,1,2]`、`[2,3,1]` 。 + +整数数组的 **下一个排列** 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 **下一个排列** 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。 + +* 例如,`arr = [1,2,3]` 的下一个排列是 `[1,3,2]` 。 +* 类似地,`arr = [2,3,1]` 的下一个排列是 `[3,1,2]` 。 +* 而 `arr = [3,2,1]` 的下一个排列是 `[1,2,3]` ,因为 `[3,2,1]` 不存在一个字典序更大的排列。 + +给你一个整数数组 `nums` ,找出 `nums` 的下一个排列。 + +必须 **[原地](https://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95)** 修改,只允许使用额外常数空间。 + + **示例 1:** + +``` +输入:nums = [1,2,3] +输出:[1,3,2] +``` + + **示例 2:** + +``` +输入:nums = [3,2,1] +输出:[1,2,3] +``` + + **示例 3:** + +``` +输入:nums = [1,1,5] +输出:[1,5,1] +``` + + **提示:** + +* `1 <= nums.length <= 100` +* `0 <= nums[i] <= 100` + +--- + +算法步骤: + +1. 从后往前找到第一个升序的元素对,分别定义其下标为 `index - 1` 和 `index`,即 `nums[index - 1] < nums[i]` +2. 重新从后往前找到第一个大于 `nums[index - 1]` 的元素,定义其下标为 `i`, 即元素为 `nums[i]` 并交换 `nums[index - 1]`,注意此时不一定能找到,因此也不一定发生交换,所以不能从前往后找。 +3. 接着反转 `[index , n - 1]` 范围内的所有元素,因为 `[index, n - 1]` 范围内的元素经过第一步筛选后一定是非升序的,下一个更大的排列一定是升序的。 + +```Java +class Solution { + public void nextPermutation(int[] nums) { + int n = nums.length, index = n - 1; + while(index > 0){ + if(nums[index] > nums[index - 1]){ + break; + } + index--; + } + if(index != 0){ + for(int i = n - 1; i >= index; i--){ + if(nums[index - 1] < nums[i]){ + swap(nums, index - 1, i); + break; + } + } + } + int left = index, right = n - 1; + while(left < right){ + swap(nums, left++, right--); + } + } + + private void swap(int[] nums, int i, int j){ + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } +} +``` + + + +Go: + +```go +func nextPermutation(nums []int) { + i, j := 0, len(nums) - 1 + for ;j > 0; j-- { + if nums[j] > nums[j - 1] { + i = j - 1 + break + } + } + for k := len(nums) - 1; k >= j; k-- { + if nums[k] > nums[i] { + nums[i], nums[k] = nums[k], nums[i] + break + } + } + + for k := len(nums) - 1; k > j; k, j = k - 1, j + 1 { + nums[j], nums[k] = nums[k], nums[j] + } + return +} +``` + diff --git "a/Methodology/3143. \346\255\243\346\226\271\345\275\242\344\270\255\347\232\204\346\234\200\345\244\232\347\202\271\346\225\260.md" "b/Methodology/3143. \346\255\243\346\226\271\345\275\242\344\270\255\347\232\204\346\234\200\345\244\232\347\202\271\346\225\260.md" new file mode 100644 index 0000000..4ca8adf --- /dev/null +++ "b/Methodology/3143. \346\255\243\346\226\271\345\275\242\344\270\255\347\232\204\346\234\200\345\244\232\347\202\271\346\225\260.md" @@ -0,0 +1,99 @@ +#### 3143. 正方形中的最多点数 + +难度:中等 + +--- + +给你一个二维数组 `points` 和一个字符串 `s` ,其中 `points[i]` 表示第 `i` 个点的坐标,`s[i]` 表示第 `i` 个点的 **标签**  。 + +如果一个正方形的中心在 `(0, 0)` ,所有边都平行于坐标轴,且正方形内  **不**  存在标签相同的两个点,那么我们称这个正方形是  **合法**  的。 + +请你返回 **合法**  正方形中可以包含的 **最多**  点数。 + + **注意:** + +* 如果一个点位于正方形的边上或者在边以内,则认为该点位于正方形内。 +* 正方形的边长可以为零。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2024/03/29/3708-tc1.png) + +**输入:**points = \[\[2,2\],\[-1,-2\],\[-4,4\],\[-3,1\],\[3,-3\]\], s = "abdca" + +**输出:**2 + + **解释:** + +边长为 4 的正方形包含两个点 `points[0]` 和 `points[1]` 。 + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2024/03/29/3708-tc2.png) + +**输入:**points = \[\[1,1\],\[-2,-2\],\[-2,2\]\], s = "abb" + +**输出:**1 + + **解释:** + +边长为 2 的正方形包含 1 个点 `points[0]` 。 + + **示例 3:** + +**输入:**points = \[\[1,1\],\[-1,-1\],\[2,-2\]\], s = "ccd" + +**输出:**0 + + **解释:** + +任何正方形都无法只包含 `points[0]` 和 `points[1]` 中的一个点,所以合法正方形中都不包含任何点。 + + **提示:** + +* `1 <= s.length, points.length <= 10^5` +* `points[i].length == 2` +* `-10^9 <= points[i][0], points[i][1] <= 10^9` +* `s.length == points.length` +* `points` 中的点坐标互不相同。 +* `s` 只包含小写英文字母。 + +--- + +维护次小半径: + +遍历所有坐标,记录所有标签的次小半径,最后统计**各个标签的最小半径**小于**次小半径**的个数 + +```Go +func maxPointsInsideSquare(points [][]int, s string) int { + alphabet := [26]int{} + for i := range alphabet { + alphabet[i] = math.MaxInt + } + minn := math.MaxInt + for i := range points { + ch := s[i] + dis := max(abs(points[i][0]), abs(points[i][1])) + if dis < alphabet[ch - 'a'] { + minn = min(minn, alphabet[ch - 'a']) + alphabet[ch - 'a'] = dis + } else if dis < minn { + minn = dis + } + } + res := 0 + for i := 0; i < 26; i++ { + if alphabet[i] < minn { + res++ + } + } + return res +} + +func abs(i int) int { + if i < 0 { + return -i + } + return i +} +``` \ No newline at end of file diff --git "a/Methodology/56. \345\220\210\345\271\266\345\214\272\351\227\264.md" "b/Methodology/56. \345\220\210\345\271\266\345\214\272\351\227\264.md" new file mode 100644 index 0000000..18795d2 --- /dev/null +++ "b/Methodology/56. \345\220\210\345\271\266\345\214\272\351\227\264.md" @@ -0,0 +1,61 @@ +#### 56. 合并区间 + +难度:中等 + +--- + +以数组 `intervals` 表示若干个区间的集合,其中单个区间为 `intervals[i] = [starti, endi]` 。请你合并所有重叠的区间,并返回 _一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间_ 。 + + **示例 1:** + +``` +输入:intervals = [[1,3],[2,6],[8,10],[15,18]] +输出:[[1,6],[8,10],[15,18]] +解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +``` + + **示例 2:** + +``` +输入:intervals = [[1,4],[4,5]] +输出:[[1,5]] +解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 +``` + + **提示:** + +* `1 <= intervals.length <= 10^4` +* `intervals[i].length == 2` +* `0 <= starti <= endi <= 10^4` + +--- + +类似贪心: + +先按左区间排序,接着分三种情况讨论: + +1. 当前的右区间在下一个元素的左区间之前 +2. 当前的右区间在下一个元素的左右区间之间 +3. 当前的右区间在下一个元素的右区间之后 + +更简化的方案是将 List 中最后一个元素的右值与当前元素的左值比较。 + +```Java +class Solution { + public int[][] merge(int[][] intervals) { + Arrays.sort(intervals, (int[] a, int[] b) -> (a[0] - b[0])); + List res = new ArrayList<>(); + int index = 0, n = intervals.length; + while(index < n){ + int start = intervals[index][0], end = intervals[index][1]; + if(res.size() == 0 || res.get(res.size() - 1)[1] < start){ + res.add(new int[]{start, end}); + } else { + res.get(res.size() - 1)[1] = Math.max(res.get(res.size() - 1)[1], end); + } + index++; + } + return res.toArray(new int[res.size()][2]); + } +} +``` \ No newline at end of file diff --git "a/Methodology/670. \346\234\200\345\244\247\344\272\244\346\215\242.md" "b/Methodology/670. \346\234\200\345\244\247\344\272\244\346\215\242.md" new file mode 100644 index 0000000..fbcf4cd --- /dev/null +++ "b/Methodology/670. \346\234\200\345\244\247\344\272\244\346\215\242.md" @@ -0,0 +1,121 @@ +#### 670. 最大交换 + +难度:中等 + +--- + +给定一个非负整数,你 **至多** 可以交换一次数字中的任意两位。返回你能得到的最大值。 + + **示例 1 :** + +``` +输入: 2736 +输出: 7236 +解释: 交换数字2和数字7。 +``` + + **示例 2 :** + +``` +输入: 9973 +输出: 9973 +解释: 不需要交换。 +``` + + **注意:** + +1. 给定数字的范围是 \[0, 10^8\] + +--- + +方法一,方法论: + +从左向右遍历,找到第一次升序的下标,先尽可能地向后找到最大值,再尽可能向前找小于该最大值的最小值,然后交换两者位置。 + +```java +class Solution { + public int maximumSwap(int num) { + String s = "" + num; + int n = s.length(), flag = -1, index = 0; + while(index < n - 1){ + if(s.charAt(index) < s.charAt(index + 1)){ + flag = index; + int temp = index + 1, maxn = s.charAt(temp) - '0', maxn_index = temp; + while(temp < n){ + if(s.charAt(temp) - '0' >= maxn){ + maxn_index = temp; + maxn = s.charAt(temp) - '0'; + } + temp++; + } + while(index >= 0){ + if(s.charAt(index) < s.charAt(maxn_index)){ + flag = index; + } + index--; + } + return Integer.parseInt(swap(s, flag, maxn_index)); + } + index++; + } + return num; + } + + private String swap(String s, int index1, int index2){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < s.length(); i++){ + if(i == index1){ + sb.append(s.charAt(index2)); + } else if(i == index2){ + sb.append(s.charAt(index1)); + } else { + sb.append(s.charAt(i)); + } + } + return sb.toString(); + } + +} +``` + + + +方法二,贪心: + +从右向左遍历,标记已扫描的数字的最大值,如果当前值大于最大值,则更新最大值下标;如果当前值小于最大值,记录两个数的下标。 + +```Java +class Solution { + public int maximumSwap(int num) { + String s = "" + num; + int n = s.length(), maxn_index = n - 1, index1 = -1, index2 = maxn_index; + for(int i = n - 1; i >= 0; i--){ + if(s.charAt(i) > s.charAt(maxn_index)){ + maxn_index = i; + } else if(s.charAt(i) < s.charAt(maxn_index)){ + index1 = i; + index2 = maxn_index; + } + } + if(index1 != -1){ + return Integer.parseInt(swap(s, index1, index2)); + } + return num; + } + + private String swap(String s, int index1, int index2){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < s.length(); i++){ + if(i == index1){ + sb.append(s.charAt(index2)); + } else if(i == index2){ + sb.append(s.charAt(index1)); + } else { + sb.append(s.charAt(i)); + } + } + return sb.toString(); + } + +} +``` \ No newline at end of file diff --git "a/Methodology/89. \346\240\274\351\233\267\347\274\226\347\240\201.md" "b/Methodology/89. \346\240\274\351\233\267\347\274\226\347\240\201.md" new file mode 100644 index 0000000..3000860 --- /dev/null +++ "b/Methodology/89. \346\240\274\351\233\267\347\274\226\347\240\201.md" @@ -0,0 +1,80 @@ +#### 89. 格雷编码 + +难度:中等 + +--- + + **n 位格雷码序列** 是一个由 `2^n` 个整数组成的序列,其中: + +* 每个整数都在范围 `[0, 2^n - 1]` 内(含 `0` 和 `2^n - 1`) +* 第一个整数是 `0` +* 一个整数在序列中出现 **不超过一次** +* 每对 **相邻** 整数的二进制表示 **恰好一位不同** ,且 +* **第一个** 和 **最后一个** 整数的二进制表示 **恰好一位不同** + +给你一个整数 `n` ,返回任一有效的 **n 位格雷码序列** 。 + + **示例 1:** + +``` +输入:n = 2 +输出:[0,1,3,2] +解释: +[0,1,3,2] 的二进制表示是 [00,01,11,10] 。 +- 00 和 01 有一位不同 +- 01 和 11 有一位不同 +- 11 和 10 有一位不同 +- 10 和 00 有一位不同 +[0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。 +- 00 和 10 有一位不同 +- 10 和 11 有一位不同 +- 11 和 01 有一位不同 +- 01 和 00 有一位不同 +``` + + **示例 2:** + +``` +输入:n = 1 +输出:[0,1] +``` + + **提示:** + +* `1 <= n <= 16` + +--- + +找规律: + +通过规律发现,根据前 `k` 位的格雷码通过**对称**的方式可以构造出 `k + 1` 位的格雷码。例如, + +``` +0 0 0 +0 0 1 +0 1 1 +0 1 0 +1 1 0 +1 1 1 +1 0 1 +1 0 0 +``` + +前两行的后一位(0,1)对称可得到第三四行的后一位(1,0),前四行的后两位(00,01,11,10)对称可得到第五至八行的后两位(10,11,01,00)。 + +```Java +class Solution { + public List grayCode(int n) { + List ans = new ArrayList<>(); + ans.add(0); + int head = 1; + for(int i = 0; i < n; i++){ + for(int j = ans.size() - 1; j >= 0; j--){ + ans.add(head + ans.get(j)); + } + head <<= 1; + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Monotonic Stack/739. \346\257\217\346\227\245\346\270\251\345\272\246.md" "b/Monotonic Stack/739. \346\257\217\346\227\245\346\270\251\345\272\246.md" new file mode 100644 index 0000000..ac13e1c --- /dev/null +++ "b/Monotonic Stack/739. \346\257\217\346\227\245\346\270\251\345\272\246.md" @@ -0,0 +1,85 @@ +#### 739. 每日温度 + +难度:中等 + +--- + +给定一个整数数组 `temperatures` ,表示每天的温度,返回一个数组 `answer` ,其中 `answer[i]` 是指对于第 `i` 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 `0` 来代替。 + + **示例 1:** + +``` +输入: temperatures = [73,74,75,71,69,72,76,73] +输出: [1,1,4,2,1,1,0,0] +``` + + **示例 2:** + +``` +输入: temperatures = [30,40,50,60] +输出: [1,1,1,0] +``` + + **示例 3:** + +``` +输入: temperatures = [30,60,90] +输出: [1,1,0] +``` + + **提示:** + +* `1 <= temperatures.length <= 10^5` +* `30 <= temperatures[i] <= 100` + +--- + +方法一,动态规划 + 双指针: + +从后向前遍历,在每个循环中会**跳过一定的区域**,思路类似于[这个](https://leetcode.cn/problems/daily-temperatures/solution/jie-ti-si-lu-by-pulsaryu/)。 + +时间复杂度$O(N)$,空间复杂度$O(N)$。 + +![](https://raw.githubusercontent.com/CompetitiveLin/ImageHostingService/picgo/imgs/202304091057244.png) + +```Java +class Solution { + public int[] dailyTemperatures(int[] temperatures) { + int n = temperatures.length; + int[] dp = new int[n]; + dp[n - 1] = 0; + for(int i = n - 2; i >= 0; i--){ + int index = i + 1, cnt = 1; + while(dp[index] != 0 && temperatures[index] <= temperatures[i]){ + cnt += dp[index]; + index += dp[index]; + } + if(temperatures[index] <= temperatures[i]) dp[i] = 0; + else dp[i] = cnt; + } + return dp; + } +} +``` + +方法二,单调栈: + +维护一个存储下标的单调栈,**从栈底到栈顶的下标对应的温度列表中的温度依次递减**。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。每次弹出栈中的元素(下标),都能得到该下标的答案(下一次温度更高的下标,为当前下标减去弹出元素的下标)。 + +```java +class Solution { + public int[] dailyTemperatures(int[] temperatures) { + int n = temperatures.length; + ArrayDeque stack = new ArrayDeque<>(); + int[] ans = new int[n]; + for(int i = 0; i < n; i++){ + while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){ + ans[stack.peek()] = i - stack.pop(); + } + stack.push(i); + } + return ans; + } +} +``` + diff --git "a/Prefix Sum/1171. \344\273\216\351\223\276\350\241\250\344\270\255\345\210\240\345\216\273\346\200\273\345\222\214\345\200\274\344\270\272\351\233\266\347\232\204\350\277\236\347\273\255\350\212\202\347\202\271.md" "b/Prefix Sum/1171. \344\273\216\351\223\276\350\241\250\344\270\255\345\210\240\345\216\273\346\200\273\345\222\214\345\200\274\344\270\272\351\233\266\347\232\204\350\277\236\347\273\255\350\212\202\347\202\271.md" new file mode 100644 index 0000000..b384cfb --- /dev/null +++ "b/Prefix Sum/1171. \344\273\216\351\223\276\350\241\250\344\270\255\345\210\240\345\216\273\346\200\273\345\222\214\345\200\274\344\270\272\351\233\266\347\232\204\350\277\236\347\273\255\350\212\202\347\202\271.md" @@ -0,0 +1,81 @@ +#### 1171. 从链表中删去总和值为零的连续节点 + +难度:中等 + +--- + +给你一个链表的头节点 `head`,请你编写代码,反复删去链表中由 **总和**  值为 `0` 的连续节点组成的序列,直到不存在这样的序列为止。 + +删除完毕后,请你返回最终结果链表的头节点。 + +你可以返回任何满足题目要求的答案。 + +(注意,下面示例中的所有序列,都是对 `ListNode` 对象序列化的表示。) + + **示例 1:** + +``` +输入:head = [1,2,-3,3,1] +输出:[3,1] +提示:答案 [1,2,1] 也是正确的。 +``` + + **示例 2:** + +``` +输入:head = [1,2,3,-3,4] +输出:[1,2,4] +``` + + **示例 3:** + +``` +输入:head = [1,2,3,-3,-2] +输出:[1] +``` + + **提示:** + +* 给你的链表中可能有 `1` 到 `1000` 个节点。 +* 对于链表中的每个节点,节点的值:`-1000 <= node.val <= 1000`. + +--- + +哈希表 + 前缀和: + +删去**连续节点组成的序列**,应该要想到前缀和。 + +并且需要两次遍历:第一次遍历记录当前前缀和的最后节点以哈希表的数据结构存储;第二次遍历从哈希表中取出相应节点,当前节点的下一个节点是哈希表中的节点的下一个节点(同样适用于当前节点!)。 + +此外,需要一个 dummy 节点,其节点的下一个节点指向 `head` 节点,以解决 `[1, -1]` 的情况! + +```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 removeZeroSumSublists(ListNode head) { + HashMap hashmap = new HashMap<>(); + ListNode dummy = new ListNode(0); + dummy.next = head; + int presum = 0; + for(ListNode temp = dummy; temp != null; temp = temp.next){ + presum += temp.val; + hashmap.put(presum, temp); + } + presum = 0; + for(ListNode temp = dummy; temp != null; temp = temp.next){ + presum += temp.val; + temp.next = hashmap.get(presum).next; + } + return dummy.next; + } +} +``` \ No newline at end of file diff --git "a/Prefix Sum/2559. \347\273\237\350\256\241\350\214\203\345\233\264\345\206\205\347\232\204\345\205\203\351\237\263\345\255\227\347\254\246\344\270\262\346\225\260.md" "b/Prefix Sum/2559. \347\273\237\350\256\241\350\214\203\345\233\264\345\206\205\347\232\204\345\205\203\351\237\263\345\255\227\347\254\246\344\270\262\346\225\260.md" new file mode 100644 index 0000000..35f42b8 --- /dev/null +++ "b/Prefix Sum/2559. \347\273\237\350\256\241\350\214\203\345\233\264\345\206\205\347\232\204\345\205\203\351\237\263\345\255\227\347\254\246\344\270\262\346\225\260.md" @@ -0,0 +1,73 @@ +#### 2559. 统计范围内的元音字符串数 + +难度:中等 + +--- + +给你一个下标从 **0** 开始的字符串数组 `words` 以及一个二维整数数组 `queries` 。 + +每个查询 `queries[i] = [li, ri]` 会要求我们统计在 `words` 中下标在 `li` 到 `ri` 范围内( **包含** 这两个值)并且以元音开头和结尾的字符串的数目。 + +返回一个整数数组,其中数组的第 `i` 个元素对应第 `i` 个查询的答案。 + + **注意:** 元音字母是 `'a'`、`'e'`、`'i'`、`'o'` 和 `'u'` 。 + + **示例 1:** + +``` +输入:words = ["aba","bcb","ece","aa","e"], queries = [[0,2],[1,4],[1,1]] +输出:[2,3,0] +解释:以元音开头和结尾的字符串是 "aba"、"ece"、"aa" 和 "e" 。 +查询 [0,2] 结果为 2(字符串 "aba" 和 "ece")。 +查询 [1,4] 结果为 3(字符串 "ece"、"aa"、"e")。 +查询 [1,1] 结果为 0 。 +返回结果 [2,3,0] 。 +``` + + **示例 2:** + +``` +输入:words = ["a","e","i"], queries = [[0,2],[0,1],[2,2]] +输出:[3,2,1] +解释:每个字符串都满足这一条件,所以返回 [3,2,1] 。 +``` + + **提示:** + +* `1 <= words.length <= 10^5` +* `1 <= words[i].length <= 40` +* `words[i]` 仅由小写英文字母组成 +* `sum(words[i].length) <= 3 * 10^5` +* `1 <= queries.length <= 10^5` +* `0 <= queries[j][0] <= queries[j][1] < words.length` + +--- + +前缀和: + +本题是区间查询,因此可以考虑前缀和实现区间查询。开辟一个长度为 `n + 1` 或 `n` 的一维数组即可。 + +```Java +class Solution { + public int[] vowelStrings(String[] words, int[][] queries) { + int n = words.length, m = queries.length; + int[] dp = new int[n + 1]; + for(int i = 1; i < n + 1; i++) dp[i] = dp[i - 1] + check(words[i - 1]); + int[] ans = new int[m]; + for(int i = 0; i < m; i++){ + ans[i] = dp[queries[i][1] + 1] - dp[queries[i][0]]; + } + return ans; + } + + public int check(String word){ + int n = word.length(); + if(word.charAt(0) == 'a' || word.charAt(0) == 'e' || word.charAt(0) == 'i' || word.charAt(0) == 'o' || word.charAt(0) == 'u'){ + if(word.charAt(n - 1) == 'a' || word.charAt(n - 1) == 'e' || word.charAt(n - 1) == 'i' || word.charAt(n - 1) == 'o' || word.charAt(n - 1) == 'u'){ + return 1; + } + } + return 0; + } +} +``` \ No newline at end of file diff --git "a/Prefix Sum/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" "b/Prefix Sum/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" new file mode 100644 index 0000000..00fbe8f --- /dev/null +++ "b/Prefix Sum/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.md" @@ -0,0 +1,60 @@ +#### 53. 最大子数组和 + +难度:中等 + +--- + +给你一个整数数组 `nums` ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + + **子数组** 是数组中的一个连续部分。 + + **示例 1:** + +``` +输入:nums = [-2,1,-3,4,-1,2,1,-5,4] +输出:6 +解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 +``` + + **示例 2:** + +``` +输入:nums = [1] +输出:1 +``` + + **示例 3:** + +``` +输入:nums = [5,4,-1,7,8] +输出:23 +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `-10^4 <= nums[i] <= 10^4` + + **进阶:** 如果你已经实现复杂度为 `O(n)` 的解法,尝试使用更为精妙的 **分治法** 求解。 + +--- + +前缀和: + +顺序遍历并记录前缀和的最小值,与当前前缀和进行对比,取差值最大的即可。注意的是**前缀和数组长度为原数组长度 + 1**。 + +```Java +class Solution { + public int maxSubArray(int[] nums) { + int n = nums.length; + int[] prefix = new int[n + 1]; + int current = 0, res = nums[0]; + for(int i = 1; i < n + 1; i++){ + prefix[i] = prefix[i - 1] + nums[i - 1]; + res = Math.max(res, prefix[i] - current); + current = Math.min(current, prefix[i]); + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Priority Queue/1054. \350\267\235\347\246\273\347\233\270\347\255\211\347\232\204\346\235\241\345\275\242\347\240\201.md" "b/Priority Queue/1054. \350\267\235\347\246\273\347\233\270\347\255\211\347\232\204\346\235\241\345\275\242\347\240\201.md" new file mode 100644 index 0000000..c9f94f6 --- /dev/null +++ "b/Priority Queue/1054. \350\267\235\347\246\273\347\233\270\347\255\211\347\232\204\346\235\241\345\275\242\347\240\201.md" @@ -0,0 +1,71 @@ +#### 1054. 距离相等的条形码 + +难度:中等 + +--- + +在一个仓库里,有一排条形码,其中第 `i` 个条形码为 `barcodes[i]`。 + +请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。 + + **示例 1:** + +``` +输入:barcodes = [1,1,1,2,2,2] +输出:[2,1,2,1,2,1] +``` + + **示例 2:** + +``` +输入:barcodes = [1,1,1,1,2,2,3,3] +输出:[1,3,1,3,2,1,2,1] +``` + + **提示:** + +* `1 <= barcodes.length <= 10000` +* `1 <= barcodes[i] <= 10000` + +--- + +哈希表 + 优先队列: + +最初的思路:找到剩余数量最多的元素并尽可能优先排列。 + +借助优先队列(最大堆),每次取优先队列(最大堆)中**剩余数量最多**和**第二多**的元素放入数组中,保证了相邻元素不重复。再考虑一下特殊情况,原数组的个数为奇数个时,意味着最后一个的元素直接放在数组的最后一个即可。 + +```Java +class Solution { + public int[] rearrangeBarcodes(int[] barcodes) { + int n = barcodes.length; + HashMap hashmap = new HashMap<>(); + for(int barcode: barcodes){ + hashmap.put(barcode, hashmap.getOrDefault(barcode, 0) + 1); + } + PriorityQueue pq = new PriorityQueue<>((a, b) -> (hashmap.get(b) - hashmap.get(a))); + for(Integer i: hashmap.keySet()){ + pq.add(i); + } + int[] ans = new int[n]; + for(int i = 0; i < n; i += 2){ + if(i == n - 1){ + ans[i] = pq.poll(); + } else { + int temp1 = pq.poll(), temp2 = pq.poll(); + ans[i] = temp1; + ans[i + 1] = temp2; + if(hashmap.get(temp1) > 1){ + hashmap.put(temp1, hashmap.get(temp1) - 1); + pq.add(temp1); + } + if(hashmap.get(temp2) > 1){ + hashmap.put(temp2, hashmap.get(temp2) - 1); + pq.add(temp2); + } + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Priority Queue/1792. \346\234\200\345\244\247\345\271\263\345\235\207\351\200\232\350\277\207\347\216\207.md" "b/Priority Queue/1792. \346\234\200\345\244\247\345\271\263\345\235\207\351\200\232\350\277\207\347\216\207.md" new file mode 100644 index 0000000..619df91 --- /dev/null +++ "b/Priority Queue/1792. \346\234\200\345\244\247\345\271\263\345\235\207\351\200\232\350\277\207\347\216\207.md" @@ -0,0 +1,75 @@ +#### 1792. 最大平均通过率 + +难度:中等 + +--- + +一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试。给你一个二维数组 `classes` ,其中 `classes[i] = [passi, totali]` ,表示你提前知道了第 `i` 个班级总共有 `totali` 个学生,其中只有 `passi` 个学生可以通过考试。 + +给你一个整数 `extraStudents` ,表示额外有 `extraStudents` 个聪明的学生,他们 **一定**  能通过任何班级的期末考。你需要给这 `extraStudents` 个学生每人都安排一个班级,使得 **所有**  班级的 **平均**  通过率 **最大**  。 + +一个班级的  **通过率**  等于这个班级通过考试的学生人数除以这个班级的总人数。 **平均通过率**  是所有班级的通过率之和除以班级数目。 + +请你返回在安排这 `extraStudents` 个学生去对应班级后的 **最大**  平均通过率。与标准答案误差范围在 `10^-5` 以内的结果都会视为正确结果。 + + **示例 1:** + +``` +输入:classes = [[1,2],[3,5],[2,2]], extraStudents = 2 +输出:0.78333 +解释:你可以将额外的两个学生都安排到第一个班级,平均通过率为 (3/4 + 3/5 + 2/2) / 3 = 0.78333 。 +``` + + **示例 2:** + +``` +输入:classes = [[2,4],[3,9],[4,5],[2,10]], extraStudents = 4 +输出:0.53485 +``` + + **提示:** + +* `1 <= classes.length <= 10^5` +* `classes[i].length == 2` +* `1 <= passi <= totali <= 10^5` +* `1 <= extraStudents <= 10^5` + +--- + +优先队列(增量大顶堆): + +计算 `classes` 里每个班级考试通过率的**增量值**,存入优先队列中,每次取增量最大的一组通过率并安排一位聪明的学生,再重新计算考试通过率并存入优先队列中。直到聪明的学生都被分配完为止。 + +```java +class Solution { +    public double maxAverageRatio(int[][] classes, int extraStudents) { +        PriorityQueue pq = new PriorityQueue<>((a, b) ->{ +            double temp1 = (a[0] + 1) * 1.0 / (a[1] + 1) - a[0] * 1.0 / a[1]; +            double temp2 = (b[0] + 1) * 1.0 / (b[1] + 1) - b[0] * 1.0 / b[1]; +            return Double.compare(temp2, temp1); +        }); +        for(int[] temp: classes){ +            pq.offer(temp); +        } +        while(extraStudents-- != 0){ +            int[] temp = pq.poll(); +            temp[0]++; +            temp[1]++; +            pq.offer(temp); +        } +        double ans = 0.0; +        while(!pq.isEmpty()){ +            int[] temp = pq.poll(); +            ans += temp[0] * 1.0 / temp[1]; +        } +        return ans / classes.length; +    } +} +``` + +注意: + +- `public static int compare(double num1, double num2)`,用法:`Double.compare(d1, d2)` +- `public int compareTo(Double anotherDouble)`,用法:`D1.compareTo(D2)` + +使用方法2需要**手动装箱**。 \ No newline at end of file diff --git "a/Priority Queue/1801. \347\247\257\345\216\213\350\256\242\345\215\225\344\270\255\347\232\204\350\256\242\345\215\225\346\200\273\346\225\260.md" "b/Priority Queue/1801. \347\247\257\345\216\213\350\256\242\345\215\225\344\270\255\347\232\204\350\256\242\345\215\225\346\200\273\346\225\260.md" new file mode 100644 index 0000000..3e58370 --- /dev/null +++ "b/Priority Queue/1801. \347\247\257\345\216\213\350\256\242\345\215\225\344\270\255\347\232\204\350\256\242\345\215\225\346\200\273\346\225\260.md" @@ -0,0 +1,99 @@ +#### 1801. 积压订单中的订单总数 + +难度:中等 + +--- + +给你一个二维整数数组 `orders` ,其中每个 `orders[i] = [pricei, amounti, orderTypei]` 表示有 `amounti` 笔类型为 `orderTypei` 、价格为 `pricei` 的订单。 + +订单类型 `orderTypei` 可以分为两种: + +* `0` 表示这是一批采购订单 `buy` +* `1` 表示这是一批销售订单 `sell` + +注意,`orders[i]` 表示一批共计 `amounti` 笔的独立订单,这些订单的价格和类型相同。对于所有有效的 `i` ,由 `orders[i]` 表示的所有订单提交时间均早于 `orders[i+1]` 表示的所有订单。 + +存在由未执行订单组成的 **积压订单** 。积压订单最初是空的。提交订单时,会发生以下情况: + +* 如果该订单是一笔采购订单 `buy` ,则可以查看积压订单中价格 **最低** 的销售订单 `sell` 。如果该销售订单 `sell` 的价格 **低于或等于** 当前采购订单 `buy` 的价格,则匹配并执行这两笔订单,并将销售订单 `sell` 从积压订单中删除。否则,采购订单 `buy` 将会添加到积压订单中。 +* 反之亦然,如果该订单是一笔销售订单 `sell` ,则可以查看积压订单中价格 **最高** 的采购订单 `buy` 。如果该采购订单 `buy` 的价格 **高于或等于** 当前销售订单 `sell` 的价格,则匹配并执行这两笔订单,并将采购订单 `buy` 从积压订单中删除。否则,销售订单 `sell` 将会添加到积压订单中。 + +输入所有订单后,返回积压订单中的 **订单总数** 。由于数字可能很大,所以需要返回对 `10^9 + 7` 取余的结果。 + + **示例 1:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/21/ex1.png) +``` +输入:orders = [[10,5,0],[15,2,1],[25,1,1],[30,4,0]] +输出:6 +解释:输入订单后会发生下述情况: +- 提交 5 笔采购订单,价格为 10 。没有销售订单,所以这 5 笔订单添加到积压订单中。 +- 提交 2 笔销售订单,价格为 15 。没有采购订单的价格大于或等于 15 ,所以这 2 笔订单添加到积压订单中。 +- 提交 1 笔销售订单,价格为 25 。没有采购订单的价格大于或等于 25 ,所以这 1 笔订单添加到积压订单中。 +- 提交 4 笔采购订单,价格为 30 。前 2 笔采购订单与价格最低(价格为 15)的 2 笔销售订单匹配,从积压订单中删除这 2 笔销售订单。第 3 笔采购订单与价格最低的 1 笔销售订单匹配,销售订单价格为 25 ,从积压订单中删除这 1 笔销售订单。积压订单中不存在更多销售订单,所以第 4 笔采购订单需要添加到积压订单中。 +最终,积压订单中有 5 笔价格为 10 的采购订单,和 1 笔价格为 30 的采购订单。所以积压订单中的订单总数为 6 。 +``` + + **示例 2:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/21/ex2.png) +``` +输入:orders = [[7,1000000000,1],[15,3,0],[5,999999995,0],[5,1,1]] +输出:999999984 +解释:输入订单后会发生下述情况: +- 提交 109 笔销售订单,价格为 7 。没有采购订单,所以这 109 笔订单添加到积压订单中。 +- 提交 3 笔采购订单,价格为 15 。这些采购订单与价格最低(价格为 7 )的 3 笔销售订单匹配,从积压订单中删除这 3 笔销售订单。 +- 提交 999999995 笔采购订单,价格为 5 。销售订单的最低价为 7 ,所以这 999999995 笔订单添加到积压订单中。 +- 提交 1 笔销售订单,价格为 5 。这笔销售订单与价格最高(价格为 5 )的 1 笔采购订单匹配,从积压订单中删除这 1 笔采购订单。 +最终,积压订单中有 (1000000000-3) 笔价格为 7 的销售订单,和 (999999995-1) 笔价格为 5 的采购订单。所以积压订单中的订单总数为 1999999991 ,等于 999999984 % (109 + 7) 。 +``` + + **提示:** + +* `1 <= orders.length <= 10^5` +* `orders[i].length == 3` +* `1 <= pricei, amounti <= 10^9` +* `orderTypei` 为 `0` 或 `1` + +--- + +优先队列 + 模拟: + +需要以数组的方式存入优先队列中,而不是以整型,否则会内存溢出。主要的思路还是模拟。 + +买和卖的过程有些类似,优化在一个函数内,但并没有降低时间复杂度。 + +```Java +class Solution { + public int getNumberOfBacklogOrders(int[][] orders) { + PriorityQueue buy = new PriorityQueue<>((a, b) -> (b[0] - a[0])); + PriorityQueue sell = new PriorityQueue<>((a, b) -> (a[0] - b[0])); + for(int[] order: orders){ + if(order[2] == 0) solve(sell, buy, order); + else solve(buy, sell, order); + } + int ans = 0; + for(PriorityQueue pq: Arrays.asList(buy, sell)){ + while(!pq.isEmpty()){ + int[] temp = pq.poll(); + ans = (ans + temp[1]) % 1000000007; + } + } + return ans; + } + + private void solve(PriorityQueue pq1, PriorityQueue pq2, int[] order){ + int price = order[0], num = order[1], flag = order[2]; + while(num > 0 && !pq1.isEmpty() && (flag == 0 ? pq1.peek()[0] <= price : pq1.peek()[0] >= price)){ + int[] temp = pq1.poll(); + int amount = Math.min(num, temp[1]); + num -= amount; + temp[1] -= amount; + if(temp[1] > 0){ + pq1.offer(temp); + } + } + if(num > 0) pq2.offer(new int[]{price, num}); + } +} +``` \ No newline at end of file diff --git "a/Priority Queue/1962. \347\247\273\351\231\244\347\237\263\345\255\220\344\275\277\346\200\273\346\225\260\346\234\200\345\260\217.md" "b/Priority Queue/1962. \347\247\273\351\231\244\347\237\263\345\255\220\344\275\277\346\200\273\346\225\260\346\234\200\345\260\217.md" new file mode 100644 index 0000000..e930b7b --- /dev/null +++ "b/Priority Queue/1962. \347\247\273\351\231\244\347\237\263\345\255\220\344\275\277\346\200\273\346\225\260\346\234\200\345\260\217.md" @@ -0,0 +1,70 @@ +#### 1962. 移除石子使总数最小 + +难度:中等 + +--- + +给你一个整数数组 `piles` ,数组 **下标从 0 开始** ,其中 `piles[i]` 表示第 `i` 堆石子中的石子数量。另给你一个整数 `k` ,请你执行下述操作 **恰好** `k` 次: + +* 选出任一石子堆 `piles[i]` ,并从中 **移除** `floor(piles[i] / 2)` 颗石子。 + + **注意:** 你可以对 **同一堆** 石子多次执行此操作。 + +返回执行 `k` 次操作后,剩下石子的 **最小** 总数。 + +`floor(x)` 为 **小于** 或 **等于** `x` 的 **最大** 整数。(即,对 `x` 向下取整)。 + + **示例 1:** + +``` +输入:piles = [5,4,9], k = 2 +输出:12 +解释:可能的执行情景如下: +- 对第 2 堆石子执行移除操作,石子分布情况变成 [5,4,5] 。 +- 对第 0 堆石子执行移除操作,石子分布情况变成 [3,4,5] 。 +剩下石子的总数为 12 。 +``` + + **示例 2:** + +``` +输入:piles = [4,3,6,7], k = 3 +输出:12 +解释:可能的执行情景如下: +- 对第 2 堆石子执行移除操作,石子分布情况变成 [4,3,3,7] 。 +- 对第 3 堆石子执行移除操作,石子分布情况变成 [4,3,3,4] 。 +- 对第 0 堆石子执行移除操作,石子分布情况变成 [2,3,3,4] 。 +剩下石子的总数为 12 。 +``` + + **提示:** + +* `1 <= piles.length <= 10^5` +* `1 <= piles[i] <= 10^4` +* `1 <= k <= 10^5` + +--- + +优先队列(最大堆): + +简单来说,就是**每次取数组中的最大值然后向下取整,重复 `k` 次**。显然借助**最大堆**的数据结构。 + +```Java +class Solution { + public int minStoneSum(int[] piles, int k) { + PriorityQueue heap = new PriorityQueue<>((a, b) -> (b - a)); + for(int pile: piles){ + heap.add(pile); + } + while(k-- != 0){ + int temp = heap.poll(); + heap.add(temp - temp / 2); + } + int res = 0; + while(!heap.isEmpty()){ + res += heap.poll(); + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Priority Queue/215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" "b/Priority Queue/215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" index e247388..69ddbf1 100644 --- "a/Priority Queue/215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" +++ "b/Priority Queue/215. \346\225\260\347\273\204\344\270\255\347\232\204\347\254\254K\344\270\252\346\234\200\345\244\247\345\205\203\347\264\240.md" @@ -39,6 +39,10 @@ - 当堆的容量等于 K 时,并且元素大/小于小/大顶堆的堆顶元素时,弹出堆顶元素,插入该元素。 +为什么不使用最大堆? + +- 因为使用最小堆的空间复杂度只有 O(K),而使用最大堆的话空间复杂度将是 O(N)! + ```java class Solution { public int findKthLargest(int[] nums, int k) { diff --git "a/Priority Queue/264. \344\270\221\346\225\260 II.md" "b/Priority Queue/264. \344\270\221\346\225\260 II.md" index 2a85edf..95d56f0 100644 --- "a/Priority Queue/264. \344\270\221\346\225\260 II.md" +++ "b/Priority Queue/264. \344\270\221\346\225\260 II.md" @@ -49,4 +49,33 @@ class Solution {         return (int)ans;     } } -``` \ No newline at end of file +``` + + + +动态规划: + +定义三个指针 *p*2, *p*3, *p*5,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。 + +```go +func nthUglyNumber(n int) int { + dp := make([]int, n + 1) + dp[1] = 1 + x2, x3, x5 := 1, 1, 1 + for i := 2; i <= n; i++ { + s2, s3, s5 := dp[x2] * 2, dp[x3] * 3, dp[x5] * 5 + dp[i] = min(min(s2, s3), s5) + if dp[i] == s2 { + x2++ + } + if dp[i] == s3 { // 不能用 else if,而是用 if。因为会有重复计算 + x3++ + } + if dp[i] == s5 { + x5++ + } + } + return dp[n] +} +``` + diff --git "a/Priority Queue/373. \346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204 K \345\257\271\346\225\260\345\255\227.md" "b/Priority Queue/373. \346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204 K \345\257\271\346\225\260\345\255\227.md" new file mode 100644 index 0000000..d015d35 --- /dev/null +++ "b/Priority Queue/373. \346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204 K \345\257\271\346\225\260\345\255\227.md" @@ -0,0 +1,73 @@ +#### 373. 查找和最小的 K 对数字 + +难度:中等 + +--- + +给定两个以 **非递减顺序排列** 的整数数组 `nums1` 和 `nums2` , 以及一个整数 `k` 。 + +定义一对值 `(u,v)`,其中第一个元素来自 `nums1`,第二个元素来自 `nums2` 。 + +请找到和最小的 `k` 个数对 `(u1,v1)`,  `(u2,v2)`  ...  `(uk,vk)` 。 + + **示例 1:** + +``` +输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3 +输出: [1,2],[1,4],[1,6] +解释: 返回序列中的前 3 对数: + [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6] +``` + + **示例 2:** + +``` +输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2 +输出: [1,1],[1,1] +解释: 返回序列中的前 2 对数: +  [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3] +``` + + **示例 3:** + +``` +输入: nums1 = [1,2], nums2 = [3], k = 3 +输出: [1,3],[2,3] +解释: 也可能序列中所有的数对都被返回:[1,3],[2,3] +``` + + **提示:** + +* `1 <= nums1.length, nums2.length <= 10^5` +* `-10^9 <= nums1[i], nums2[i] <= 10^9` +* `nums1` 和 `nums2` 均为 **升序排列** +* `1 <= k <= 10^4` +* `k <= nums1.length * nums2.length` + +--- + +优先队列: + +题干给出两个数组都是**非递减顺序排序**,借助优先队列,先默认放入 `nums1` 的所有索引,每次弹出的都是和最小的索引 `(i, j)`,再放入索引 `(i, j + 1)`。 + +```Java +class Solution { + public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { + PriorityQueue pq = new PriorityQueue<>((a, b) -> (nums1[a[0]] + nums2[a[1]] - nums1[b[0]] - nums2[b[1]])); + int n = nums1.length, m = nums2.length; + for(int i = 0; i < Math.min(n, k); i++){ + pq.offer(new int[]{i, 0}); + } + List> res = new ArrayList<>(); + while(k-- != 0 && !pq.isEmpty()){ + int[] temp = pq.poll(); + res.add(Arrays.asList(nums1[temp[0]], nums2[temp[1]])); + if(temp[1] < m - 1){ + temp[1] += 1; + pq.offer(temp); + } + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Queue/102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" "b/Queue/102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" new file mode 100644 index 0000000..0bb0f85 --- /dev/null +++ "b/Queue/102. \344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.md" @@ -0,0 +1,74 @@ +#### 102. 二叉树的层序遍历 + +难度:中等 + +--- + +给你二叉树的根节点 `root` ,返回其节点值的 **层序遍历** 。 (即逐层地,从左到右访问所有节点)。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) +``` +输入:root = [3,9,20,null,null,15,7] +输出:[[3],[9,20],[15,7]] +``` + + **示例 2:** + +``` +输入:root = [1] +输出:[[1]] +``` + + **示例 3:** + +``` +输入:root = [] +输出:[] +``` + + **提示:** + +* 树中节点数目在范围 `[0, 2000]` 内 +* `-1000 <= Node.val <= 1000` + +--- + +队列:每一层的节点从左往右依次存到队列中,每次都取出当前层的节点值并把左右非空子节点再存到队列中即可。 + +```Go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func levelOrder(root *TreeNode) [][]int { + if root == nil { + return nil + } + res := make([][]int, 0) + queue := []*TreeNode{root} + depth := 0 + for len(queue) != 0 { + res = append(res, []int{}) + size := len(queue) + for i := 0; i < size; i++ { + node := queue[i] + res[depth] = append(res[depth], node.Val) + if node.Left != nil { + queue = append(queue, node.Left) + } + if node.Right != nil { + queue = append(queue, node.Right) + } + } + queue = queue[size:] + depth += 1 + } + return res +} +``` \ No newline at end of file diff --git "a/Queue/225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" "b/Queue/225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" new file mode 100644 index 0000000..6f5063b --- /dev/null +++ "b/Queue/225. \347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.md" @@ -0,0 +1,101 @@ +#### 225. 用队列实现栈 + +难度:简单 + +--- + +请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(`push`、`top`、`pop` 和 `empty`)。 + +实现 `MyStack` 类: + +* `void push(int x)` 将元素 x 压入栈顶。 +* `int pop()` 移除并返回栈顶元素。 +* `int top()` 返回栈顶元素。 +* `boolean empty()` 如果栈是空的,返回 `true` ;否则,返回 `false` 。 + + **注意:** + +* 你只能使用队列的基本操作 —— 也就是 `push to back`、`peek/pop from front`、`size` 和 `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` 次 `push`、`pop`、`top` 和 `empty` +* 每次调用 `pop` 和 `top` 都保证栈不为空 + + **进阶:** 你能否仅用一个队列来实现栈。 + +--- + +队列: + +固定第一个队列为主队列,第二个队列为辅助队列; + +- 调用 `push()` 时,每次都向主队列插入数据; +- 调用 `pop()` 时,将主队列里的前 `size() - 1` 个数按队列弹出的顺序插入到辅助队列中,清空主队列后再交换主队列和辅助队列; +- 调用 `top()` 时,先调用 `pop()` 方法保存变量,再插入到主队列中,最后返回该变量; +- 调用 `isEmpty()` 时,直接调用主函数的 `isEmpty()` 方法即可。 + +```Java +class MyStack { + ArrayDeque q1, q2; + + public MyStack() { + q1 = new ArrayDeque(); + q2 = new ArrayDeque(); + } + + public void push(int x) { + q1.offer(x); + } + + public int pop() { + int sz = q1.size(); + while(--sz >= 1){ + q2.offer(q1.poll()); + } + ArrayDeque temp = q1; + q1 = q2; + q2 = temp; + return q2.poll(); + } + + public int top() { + int temp = pop(); + q1.offer(temp); + return temp; + } + + public boolean empty() { + return q1.isEmpty(); + } +} + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack obj = new MyStack(); + * obj.push(x); + * int param_2 = obj.pop(); + * int param_3 = obj.top(); + * boolean param_4 = obj.empty(); + */ +``` \ No newline at end of file diff --git a/README.md b/README.md index eb64064..da01bbd 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,11 @@ ## 使用教程 -1. 由模板项目[生成](https://github.com/shengqiangzhang/leetcode-revise/generate)自己的项目 +1. 由模板项目[生成](https://github.com/CompetitiveLin/leetcode-revise/generate)自己的项目 2. 点击生成项目下的 Settings -> Secrets -> Actions -> New repository secret,分别添加以下 Secrets: - - Name:`LEETCODE_EMAIL` - - Secret:你的LeetCode账号 - - Name:`LEETCODE_PASSWORD` - - Secret:你的LeetCode密码 + - Name:`LEETCODE_SESSION` + - Secret:已登录 LeetCode 账号的浏览器中 Cookie 名为 LEETCODE_SESSION 的值 3. 回到项目首页并进入 Actions 部分,在左侧点击`Github LeetCode Bot`,再点击蓝色提示框中的 `Run workflow`,最后点击绿色的 `Run workflow` 按钮即可 @@ -28,14 +26,209 @@ | 最近提交时间 | 题目 | 题目难度 | 提交次数| 重刷次数 | | ---- | ---- | ---- | ---- | ---- | +| 2024-10-19 20:18 | [#2165 重排数字的最小值](https://leetcode.cn/problems/smallest-value-of-the-rearranged-number) | MEDIUM | 1 | 1 | +| 2024-10-12 10:34 | [#131 分割回文串](https://leetcode.cn/problems/palindrome-partitioning) | MEDIUM | 3 | 1 | +| 2024-10-08 12:53 | [#463 岛屿的周长](https://leetcode.cn/problems/island-perimeter) | EASY | 1 | 1 | +| 2024-09-26 15:39 | [#76 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring) | HARD | 1 | 1 | +| 2024-09-23 13:50 | [#1002 查找共用字符](https://leetcode.cn/problems/find-common-characters) | EASY | 1 | 1 | +| 2024-09-21 19:34 | [#124 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum) | HARD | 1 | 1 | +| 2024-09-21 15:54 | [#25 K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group) | HARD | 2 | **2** | +| 2024-09-13 19:45 | [#236 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree) | MEDIUM | 4 | **3** | +| 2024-09-06 11:31 | [#18 四数之和](https://leetcode.cn/problems/4sum) | MEDIUM | 5 | **2** | +| 2024-09-05 09:08 | [#15 三数之和](https://leetcode.cn/problems/3sum) | MEDIUM | 8 | **4** | +| 2024-09-04 16:18 | [#105 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal) | MEDIUM | 1 | 1 | +| 2024-09-04 16:08 | [#106 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal) | MEDIUM | 1 | 1 | +| 2024-09-04 15:06 | [#42 接雨水](https://leetcode.cn/problems/trapping-rain-water) | HARD | 3 | **2** | +| 2024-08-30 15:07 | [#1143 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence) | MEDIUM | 3 | **2** | +| 2024-08-26 15:18 | [#3 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters) | MEDIUM | 10 | **3** | +| 2024-08-24 14:51 | [#718 最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray) | MEDIUM | 1 | 1 | +| 2024-08-24 10:22 | [#77 组合](https://leetcode.cn/problems/combinations) | MEDIUM | 6 | **2** | +| 2024-08-24 09:43 | [#LCR 143 子结构判断](https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof) | MEDIUM | 2 | 1 | +| 2024-08-20 18:25 | [#43 字符串相乘](https://leetcode.cn/problems/multiply-strings) | MEDIUM | 1 | 1 | +| 2024-08-20 14:57 | [#145 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal) | EASY | 1 | 1 | +| 2024-08-20 09:38 | [#138 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer) | MEDIUM | 3 | **2** | +| 2024-08-19 16:14 | [#695 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island) | MEDIUM | 4 | **2** | +| 2024-08-19 14:46 | [#958 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree) | MEDIUM | 1 | 1 | +| 2024-08-18 15:32 | [#622 设计循环队列](https://leetcode.cn/problems/design-circular-queue) | MEDIUM | 2 | 1 | +| 2024-08-18 15:02 | [#470 用 Rand7() 实现 Rand10()](https://leetcode.cn/problems/implement-rand10-using-rand7) | MEDIUM | 4 | 1 | +| 2024-08-18 13:28 | [#752 打开转盘锁](https://leetcode.cn/problems/open-the-lock) | MEDIUM | 3 | 1 | +| 2024-08-18 12:29 | [#148 排序链表](https://leetcode.cn/problems/sort-list) | MEDIUM | 2 | **2** | +| 2024-08-18 11:57 | [#215 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array) | MEDIUM | 12 | **3** | +| 2024-08-18 11:03 | [#3115 质数的最大距离](https://leetcode.cn/problems/maximum-prime-difference) | MEDIUM | 1 | 1 | +| 2024-08-16 16:59 | [#面试题 17.10 主要元素](https://leetcode.cn/problems/find-majority-element-lcci) | EASY | 5 | **2** | +| 2024-08-16 16:11 | [#135 分发糖果](https://leetcode.cn/problems/candy) | HARD | 1 | 1 | +| 2024-08-15 19:27 | [#125 验证回文串](https://leetcode.cn/problems/valid-palindrome) | EASY | 2 | 1 | +| 2024-08-15 18:56 | [#680 验证回文串 II](https://leetcode.cn/problems/valid-palindrome-ii) | EASY | 1 | 1 | +| 2024-08-15 10:18 | [#33 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array) | MEDIUM | 6 | **2** | +| 2024-08-14 11:50 | [#3152 特殊数组 II](https://leetcode.cn/problems/special-array-ii) | MEDIUM | 3 | 1 | +| 2024-08-13 13:56 | [#200 岛屿数量](https://leetcode.cn/problems/number-of-islands) | MEDIUM | 2 | **2** | +| 2024-08-07 20:16 | [#17 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number) | MEDIUM | 4 | **2** | +| 2024-08-07 16:41 | [#103 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal) | MEDIUM | 1 | 1 | +| 2024-08-07 16:24 | [#225 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues) | EASY | 4 | **2** | +| 2024-08-07 15:51 | [#152 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray) | MEDIUM | 6 | 1 | +| 2024-08-05 14:23 | [#907 子数组的最小值之和](https://leetcode.cn/problems/sum-of-subarray-minimums) | MEDIUM | 4 | 1 | +| 2024-08-05 13:29 | [#53 最大子数组和](https://leetcode.cn/problems/maximum-subarray) | MEDIUM | 5 | **3** | +| 2024-08-04 15:21 | [#206 反转链表](https://leetcode.cn/problems/reverse-linked-list) | EASY | 4 | **3** | +| 2024-08-04 15:18 | [#234 回文链表](https://leetcode.cn/problems/palindrome-linked-list) | EASY | 1 | 1 | +| 2024-08-03 17:11 | [#69 x 的平方根 ](https://leetcode.cn/problems/sqrtx) | EASY | 2 | 1 | +| 2024-08-03 15:39 | [#209 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum) | MEDIUM | 5 | **2** | +| 2024-08-03 14:54 | [#3143 正方形中的最多点数](https://leetcode.cn/problems/maximum-points-inside-the-square) | MEDIUM | 2 | 1 | +| 2024-08-02 16:21 | [#146 LRU 缓存](https://leetcode.cn/problems/lru-cache) | MEDIUM | 2 | **2** | +| 2024-08-02 15:25 | [#1046 最后一块石头的重量](https://leetcode.cn/problems/last-stone-weight) | EASY | 1 | 1 | +| 2024-08-02 13:38 | [#92 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii) | MEDIUM | 5 | **3** | +| 2024-08-01 14:39 | [#316 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters) | MEDIUM | 2 | 1 | +| 2024-08-01 10:04 | [#2740 找出分区值](https://leetcode.cn/problems/find-the-value-of-the-partition) | MEDIUM | 2 | 1 | +| 2024-08-01 09:06 | [#1291 顺次数](https://leetcode.cn/problems/sequential-digits) | MEDIUM | 1 | 1 | +| 2024-07-31 21:27 | [#423 从英文中重建数字](https://leetcode.cn/problems/reconstruct-original-digits-from-english) | MEDIUM | 1 | 1 | +| 2024-07-31 11:05 | [#3111 覆盖所有点的最少矩形数目](https://leetcode.cn/problems/minimum-rectangles-to-cover-points) | MEDIUM | 1 | 1 | +| 2024-07-30 22:49 | [#438 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string) | MEDIUM | 4 | **2** | +| 2024-07-30 21:58 | [#560 和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k) | MEDIUM | 5 | **3** | +| 2024-07-30 17:55 | [#222 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes) | EASY | 2 | **2** | +| 2024-07-30 17:06 | [#49 字母异位词分组](https://leetcode.cn/problems/group-anagrams) | MEDIUM | 3 | **2** | +| 2024-07-30 14:43 | [#62 不同路径](https://leetcode.cn/problems/unique-paths) | MEDIUM | 6 | **2** | +| 2024-07-30 14:09 | [#64 最小路径和](https://leetcode.cn/problems/minimum-path-sum) | MEDIUM | 8 | **2** | +| 2024-07-29 11:12 | [#142 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii) | MEDIUM | 3 | **3** | +| 2024-07-29 09:51 | [#202 快乐数](https://leetcode.cn/problems/happy-number) | EASY | 3 | 1 | +| 2024-07-29 09:34 | [#141 环形链表](https://leetcode.cn/problems/linked-list-cycle) | EASY | 4 | **2** | +| 2024-07-29 09:12 | [#75 颜色分类](https://leetcode.cn/problems/sort-colors) | MEDIUM | 5 | **2** | +| 2024-07-28 20:14 | [#31 下一个排列](https://leetcode.cn/problems/next-permutation) | MEDIUM | 15 | **4** | +| 2024-07-28 17:12 | [#21 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists) | EASY | 2 | **2** | +| 2024-07-27 13:16 | [#518 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii) | MEDIUM | 2 | 1 | +| 2024-07-27 12:36 | [#264 丑数 II](https://leetcode.cn/problems/ugly-number-ii) | MEDIUM | 3 | **2** | +| 2024-07-27 12:17 | [#279 完全平方数](https://leetcode.cn/problems/perfect-squares) | MEDIUM | 3 | **2** | +| 2024-07-27 11:43 | [#416 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum) | MEDIUM | 7 | **3** | +| 2024-07-26 16:54 | [#494 目标和](https://leetcode.cn/problems/target-sum) | MEDIUM | 3 | **3** | +| 2024-07-26 15:28 | [#322 零钱兑换](https://leetcode.cn/problems/coin-change) | MEDIUM | 8 | **3** | +| 2024-07-26 15:10 | [#474 一和零](https://leetcode.cn/problems/ones-and-zeroes) | MEDIUM | 1 | 1 | +| 2024-07-26 13:56 | [#1049 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii) | MEDIUM | 6 | 1 | +| 2024-07-25 20:19 | [#347 前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements) | MEDIUM | 7 | **2** | +| 2024-07-25 17:58 | [#2844 生成特殊数字的最少操作](https://leetcode.cn/problems/minimum-operations-to-make-a-special-number) | MEDIUM | 6 | 1 | +| 2024-07-24 22:32 | [#54 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix) | MEDIUM | 1 | 1 | +| 2024-07-24 09:37 | [#238 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self) | MEDIUM | 3 | 1 | +| 2024-07-20 17:03 | [#5 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring) | MEDIUM | 5 | **2** | +| 2024-07-20 15:02 | [#2 两数相加](https://leetcode.cn/problems/add-two-numbers) | MEDIUM | 7 | **2** | +| 2024-07-20 14:34 | [#424 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement) | MEDIUM | 3 | **2** | +| 2024-07-19 09:52 | [#LCR 016 无重复字符的最长子串](https://leetcode.cn/problems/wtcaE1) | MEDIUM | 5 | 1 | +| 2024-07-18 10:01 | [#213 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii) | MEDIUM | 6 | **3** | +| 2024-07-18 09:26 | [#198 打家劫舍](https://leetcode.cn/problems/house-robber) | MEDIUM | 12 | **6** | +| 2024-07-18 09:10 | [#2560 打家劫舍 IV](https://leetcode.cn/problems/house-robber-iv) | MEDIUM | 1 | 1 | +| 2024-07-17 23:16 | [#128 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence) | MEDIUM | 15 | **2** | +| 2024-07-16 20:57 | [#337 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii) | MEDIUM | 5 | **3** | +| 2024-07-15 20:07 | [#328 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list) | MEDIUM | 3 | **2** | +| 2024-07-14 11:01 | [#102 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal) | MEDIUM | 4 | **2** | +| 2024-07-14 10:40 | [#23 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists) | HARD | 7 | **2** | +| 2024-07-13 22:05 | [#24 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs) | MEDIUM | 7 | **2** | +| 2024-07-13 21:33 | [#19 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list) | MEDIUM | 8 | **3** | +| 2024-07-11 20:51 | [#56 合并区间](https://leetcode.cn/problems/merge-intervals) | MEDIUM | 6 | **3** | +| 2024-07-11 20:38 | [#155 最小栈](https://leetcode.cn/problems/min-stack) | MEDIUM | 4 | **2** | +| 2024-07-11 19:56 | [#151 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string) | MEDIUM | 3 | **2** | +| 2024-07-11 19:16 | [#150 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation) | MEDIUM | 2 | **2** | +| 2024-07-11 15:22 | [#6 Z 字形变换](https://leetcode.cn/problems/zigzag-conversion) | MEDIUM | 1 | 1 | +| 2024-07-10 20:08 | [#14 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix) | EASY | 10 | 1 | +| 2024-07-10 13:27 | [#11 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water) | MEDIUM | 9 | **3** | +| 2024-07-06 12:53 | [#39 组合总和](https://leetcode.cn/problems/combination-sum) | MEDIUM | 3 | **3** | +| 2024-07-06 10:28 | [#22 括号生成](https://leetcode.cn/problems/generate-parentheses) | MEDIUM | 4 | **4** | +| 2024-07-06 09:50 | [#3101 交替子数组计数](https://leetcode.cn/problems/count-alternating-subarrays) | MEDIUM | 2 | 1 | +| 2024-06-29 19:50 | [#122 买卖股票的最佳时机 II](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii) | MEDIUM | 3 | **3** | +| 2024-06-15 15:46 | [#300 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence) | MEDIUM | 5 | **3** | +| 2024-06-15 14:12 | [#2779 数组的最大美丽值](https://leetcode.cn/problems/maximum-beauty-of-an-array-after-applying-operation) | MEDIUM | 4 | 1 | +| 2024-06-14 21:37 | [#2786 访问数组中的位置使分数最大](https://leetcode.cn/problems/visit-array-positions-to-maximize-score) | MEDIUM | 2 | 1 | +| 2024-05-26 15:35 | [#189 轮转数组](https://leetcode.cn/problems/rotate-array) | MEDIUM | 3 | **2** | +| 2024-05-06 11:57 | [#382 链表随机节点](https://leetcode.cn/problems/linked-list-random-node) | MEDIUM | 3 | 1 | +| 2024-05-06 11:07 | [#413 等差数列划分](https://leetcode.cn/problems/arithmetic-slices) | MEDIUM | 1 | 1 | +| 2024-04-18 14:51 | [#525 连续数组](https://leetcode.cn/problems/contiguous-array) | MEDIUM | 1 | 1 | +| 2024-03-23 16:02 | [#1471 数组中的 k 个最强值](https://leetcode.cn/problems/the-k-strongest-values-in-an-array) | MEDIUM | 1 | 1 | +| 2024-03-19 19:39 | [#1838 最高频元素的频数](https://leetcode.cn/problems/frequency-of-the-most-frequent-element) | MEDIUM | 1 | 1 | +| 2024-03-18 14:24 | [#658 找到 K 个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements) | MEDIUM | 4 | 1 | +| 2024-03-18 11:20 | [#187 重复的DNA序列](https://leetcode.cn/problems/repeated-dna-sequences) | MEDIUM | 2 | 1 | +| 2024-03-15 14:19 | [#2487 从链表中移除节点](https://leetcode.cn/problems/remove-nodes-from-linked-list) | MEDIUM | 1 | 1 | +| 2024-03-12 18:00 | [#1261 在受污染的二叉树中查找元素](https://leetcode.cn/problems/find-elements-in-a-contaminated-binary-tree) | MEDIUM | 1 | 1 | +| 2024-03-10 10:17 | [#299 猜数字游戏](https://leetcode.cn/problems/bulls-and-cows) | MEDIUM | 2 | 1 | +| 2024-01-26 15:56 | [#91 解码方法](https://leetcode.cn/problems/decode-ways) | MEDIUM | 2 | 1 | +| 2024-01-26 10:13 | [#235 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree) | MEDIUM | 3 | 1 | +| 2024-01-24 11:25 | [#274 H 指数](https://leetcode.cn/problems/h-index) | MEDIUM | 3 | 1 | +| 2024-01-23 22:15 | [#26 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array) | EASY | 2 | 1 | +| 2024-01-23 22:05 | [#80 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii) | MEDIUM | 1 | 1 | +| 2024-01-22 11:10 | [#670 最大交换](https://leetcode.cn/problems/maximum-swap) | MEDIUM | 13 | **2** | +| 2024-01-20 15:18 | [#82 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii) | MEDIUM | 3 | **2** | +| 2024-01-20 13:17 | [#373 查找和最小的 K 对数字](https://leetcode.cn/problems/find-k-pairs-with-smallest-sums) | MEDIUM | 3 | **2** | +| 2023-12-29 15:53 | [#230 二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst) | MEDIUM | 1 | 1 | +| 2023-12-29 15:01 | [#78 子集](https://leetcode.cn/problems/subsets) | MEDIUM | 2 | 1 | +| 2023-12-24 12:30 | [#567 字符串的排列](https://leetcode.cn/problems/permutation-in-string) | MEDIUM | 3 | 1 | +| 2023-12-23 10:30 | [#1962 移除石子使总数最小](https://leetcode.cn/problems/remove-stones-to-minimize-the-total) | MEDIUM | 1 | 1 | +| 2023-12-20 20:08 | [#94 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal) | EASY | 1 | 1 | +| 2023-12-14 17:19 | [#226 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree) | EASY | 3 | 1 | +| 2023-12-10 12:27 | [#1137 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number) | EASY | 2 | **2** | +| 2023-12-10 12:18 | [#70 爬楼梯](https://leetcode.cn/problems/climbing-stairs) | EASY | 3 | **2** | +| 2023-12-03 10:34 | [#1423 可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards) | MEDIUM | 2 | 1 | +| 2023-12-02 11:49 | [#1094 拼车](https://leetcode.cn/problems/car-pooling) | MEDIUM | 3 | 1 | +| 2023-12-01 15:34 | [#287 寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number) | MEDIUM | 1 | 1 | +| 2023-12-01 14:44 | [#2661 找出叠涂元素](https://leetcode.cn/problems/first-completely-painted-row-or-column) | MEDIUM | 3 | 1 | +| 2023-11-13 20:11 | [#307 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable) | MEDIUM | 4 | **3** | +| 2023-11-13 15:25 | [#1234 替换子串得到平衡字符串](https://leetcode.cn/problems/replace-the-substring-for-balanced-string) | MEDIUM | 2 | 1 | +| 2023-11-10 22:06 | [#114 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list) | MEDIUM | 2 | **2** | +| 2023-11-04 16:08 | [#47 全排列 II](https://leetcode.cn/problems/permutations-ii) | MEDIUM | 5 | **4** | +| 2023-11-04 15:45 | [#46 全排列](https://leetcode.cn/problems/permutations) | MEDIUM | 7 | **5** | +| 2023-11-02 10:23 | [#153 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array) | MEDIUM | 1 | 1 | +| 2023-10-27 10:26 | [#1465 切割后面积最大的蛋糕](https://leetcode.cn/problems/maximum-area-of-a-piece-of-cake-after-horizontal-and-vertical-cuts) | MEDIUM | 2 | 1 | +| 2023-10-11 10:11 | [#2512 奖励最顶尖的 K 名学生](https://leetcode.cn/problems/reward-top-k-students) | MEDIUM | 1 | 1 | +| 2023-10-05 19:53 | [#309 买卖股票的最佳时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown) | MEDIUM | 1 | 1 | +| 2023-10-05 17:54 | [#121 买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock) | EASY | 1 | 1 | +| 2023-09-13 20:18 | [#763 划分字母区间](https://leetcode.cn/problems/partition-labels) | MEDIUM | 1 | 1 | +| 2023-09-10 12:15 | [#2594 修车的最少时间](https://leetcode.cn/problems/minimum-time-to-repair-cars) | MEDIUM | 4 | 1 | +| 2023-09-01 09:45 | [#2240 买钢笔和铅笔的方案数](https://leetcode.cn/problems/number-of-ways-to-buy-pens-and-pencils) | MEDIUM | 3 | 1 | +| 2023-08-28 19:16 | [#57 插入区间](https://leetcode.cn/problems/insert-interval) | MEDIUM | 1 | 1 | +| 2023-08-25 09:49 | [#83 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list) | EASY | 3 | 1 | +| 2023-08-10 20:25 | [#784 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation) | MEDIUM | 7 | **3** | +| 2023-07-16 12:33 | [#1679 K 和数对的最大数目](https://leetcode.cn/problems/max-number-of-k-sum-pairs) | MEDIUM | 1 | 1 | +| 2023-07-13 23:30 | [#931 下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum) | MEDIUM | 1 | 1 | +| 2023-07-12 19:42 | [#450 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst) | MEDIUM | 5 | 1 | +| 2023-07-10 22:29 | [#394 字符串解码](https://leetcode.cn/problems/decode-string) | MEDIUM | 3 | 1 | +| 2023-07-03 23:34 | [#445 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii) | MEDIUM | 3 | 1 | +| 2023-06-27 21:34 | [#912 排序数组](https://leetcode.cn/problems/sort-an-array) | MEDIUM | 13 | **6** | +| 2023-06-25 13:35 | [#162 寻找峰值](https://leetcode.cn/problems/find-peak-element) | MEDIUM | 2 | 1 | +| 2023-06-23 13:38 | [#2095 删除链表的中间节点](https://leetcode.cn/problems/delete-the-middle-node-of-a-linked-list) | MEDIUM | 3 | 1 | +| 2023-06-11 19:37 | [#1171 从链表中删去总和值为零的连续节点](https://leetcode.cn/problems/remove-zero-sum-consecutive-nodes-from-linked-list) | MEDIUM | 5 | 1 | +| 2023-06-08 21:01 | [#2390 从字符串中移除星号](https://leetcode.cn/problems/removing-stars-from-a-string) | MEDIUM | 1 | 1 | +| 2023-06-07 20:08 | [#2611 老鼠和奶酪](https://leetcode.cn/problems/mice-and-cheese) | MEDIUM | 5 | 1 | +| 2023-06-02 13:15 | [#2559 统计范围内的元音字符串数](https://leetcode.cn/problems/count-vowel-strings-in-ranges) | MEDIUM | 3 | 1 | +| 2023-06-01 16:33 | [#2517 礼盒的最大甜蜜度](https://leetcode.cn/problems/maximum-tastiness-of-candy-basket) | MEDIUM | 3 | 1 | +| 2023-05-31 21:16 | [#2300 咒语和药水的成功对数](https://leetcode.cn/problems/successful-pairs-of-spells-and-potions) | MEDIUM | 3 | 1 | +| 2023-05-29 22:29 | [#2352 相等行列对](https://leetcode.cn/problems/equal-row-and-column-pairs) | MEDIUM | 2 | 1 | +| 2023-05-25 16:03 | [#452 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons) | MEDIUM | 2 | 1 | +| 2023-05-21 14:18 | [#435 无重叠区间](https://leetcode.cn/problems/non-overlapping-intervals) | MEDIUM | 3 | 1 | +| 2023-05-14 18:13 | [#1054 距离相等的条形码](https://leetcode.cn/problems/distant-barcodes) | MEDIUM | 3 | 1 | +| 2023-05-11 10:54 | [#1016 子串能表示从 1 到 N 数字的二进制串](https://leetcode.cn/problems/binary-string-with-substrings-representing-1-to-n) | MEDIUM | 2 | 1 | +| 2023-05-07 12:24 | [#1010 总持续时间可被 60 整除的歌曲](https://leetcode.cn/problems/pairs-of-songs-with-total-durations-divisible-by-60) | MEDIUM | 3 | 1 | +| 2023-05-06 22:58 | [#1419 数青蛙](https://leetcode.cn/problems/minimum-number-of-frogs-croaking) | MEDIUM | 10 | 1 | +| 2023-05-05 13:57 | [#283 移动零](https://leetcode.cn/problems/move-zeroes) | EASY | 1 | 1 | +| 2023-05-03 22:12 | [#1003 检查替换后的词是否有效](https://leetcode.cn/problems/check-if-word-is-valid-after-substitutions) | MEDIUM | 1 | 1 | +| 2023-05-02 11:03 | [#970 强整数](https://leetcode.cn/problems/powerful-integers) | MEDIUM | 3 | 1 | +| 2023-05-01 12:27 | [#1376 通知所有员工所需的时间](https://leetcode.cn/problems/time-needed-to-inform-all-employees) | MEDIUM | 1 | 1 | +| 2023-04-30 14:00 | [#1033 移动石子直到连续](https://leetcode.cn/problems/moving-stones-until-consecutive) | MEDIUM | 3 | 1 | +| 2023-04-14 20:04 | [#1023 驼峰式匹配](https://leetcode.cn/problems/camelcase-matching) | MEDIUM | 3 | 1 | +| 2023-04-09 11:22 | [#739 每日温度](https://leetcode.cn/problems/daily-temperatures) | MEDIUM | 2 | 1 | +| 2023-04-03 22:13 | [#1053 交换一次的先前排列](https://leetcode.cn/problems/previous-permutation-with-one-swap) | MEDIUM | 4 | 1 | +| 2023-03-30 20:02 | [#1637 两点之间不包含任何点的最宽垂直区域](https://leetcode.cn/problems/widest-vertical-area-between-two-points-containing-no-points) | EASY | 2 | 1 | +| 2023-03-29 23:55 | [#1641 统计字典序元音字符串的数目](https://leetcode.cn/problems/count-sorted-vowel-strings) | MEDIUM | 1 | 1 | +| 2023-03-22 19:50 | [#1626 无矛盾的最佳球队](https://leetcode.cn/problems/best-team-with-no-conflicts) | MEDIUM | 5 | 1 | +| 2023-03-18 11:47 | [#1616 分割两个字符串得到回文串](https://leetcode.cn/problems/split-two-strings-to-make-palindrome) | MEDIUM | 5 | 1 | +| 2023-03-17 21:21 | [#89 格雷编码](https://leetcode.cn/problems/gray-code) | MEDIUM | 1 | 1 | +| 2023-03-15 11:09 | [#1615 最大网络秩](https://leetcode.cn/problems/maximal-network-rank) | MEDIUM | 1 | 1 | +| 2023-03-14 19:12 | [#1605 给定行和列的和求可行矩阵](https://leetcode.cn/problems/find-valid-matrix-given-row-and-column-sums) | MEDIUM | 1 | 1 | +| 2023-03-13 15:38 | [#289 生命游戏](https://leetcode.cn/problems/game-of-life) | MEDIUM | 2 | 1 | +| 2023-03-08 21:52 | [#LCR 166 珠宝的最高价值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof) | MEDIUM | 1 | 1 | +| 2023-03-06 21:52 | [#1653 使字符串平衡的最少删除次数](https://leetcode.cn/problems/minimum-deletions-to-make-string-balanced) | MEDIUM | 4 | 1 | +| 2023-03-03 19:26 | [#1487 保证文件名唯一](https://leetcode.cn/problems/making-file-names-unique) | MEDIUM | 5 | 1 | +| 2023-02-28 21:44 | [#1801 积压订单中的订单总数](https://leetcode.cn/problems/number-of-orders-in-the-backlog) | MEDIUM | 2 | 1 | +| 2023-02-27 17:01 | [#1144 递减元素使数组呈锯齿状](https://leetcode.cn/problems/decrease-elements-to-make-array-zigzag) | MEDIUM | 4 | 1 | +| 2023-02-20 10:19 | [#1792 最大平均通过率](https://leetcode.cn/problems/maximum-average-pass-ratio) | MEDIUM | 2 | 1 | | 2023-02-12 09:58 | [#1138 字母板上的路径](https://leetcode.cn/problems/alphabet-board-path) | MEDIUM | 4 | 1 | | 2023-02-09 17:45 | [#1797 设计一个验证系统](https://leetcode.cn/problems/design-authentication-manager) | MEDIUM | 2 | 1 | | 2023-02-08 14:28 | [#1233 删除子文件夹](https://leetcode.cn/problems/remove-sub-folders-from-the-filesystem) | MEDIUM | 1 | 1 | | 2023-01-30 19:28 | [#1669 合并两个链表](https://leetcode.cn/problems/merge-in-between-linked-lists) | MEDIUM | 2 | 1 | -| 2023-01-29 16:50 | [#264 丑数 II](https://leetcode.cn/problems/ugly-number-ii) | MEDIUM | 2 | 1 | | 2023-01-28 14:37 | [#1664 生成平衡数组的方案数](https://leetcode.cn/problems/ways-to-make-a-fair-array) | MEDIUM | 2 | 1 | | 2023-01-27 20:38 | [#503 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii) | MEDIUM | 3 | 1 | -| 2023-01-27 19:53 | [#42 接雨水](https://leetcode.cn/problems/trapping-rain-water) | HARD | 2 | 1 | | 2023-01-26 18:25 | [#1663 具有给定数值的最小字符串](https://leetcode.cn/problems/smallest-string-with-a-given-numeric-value) | MEDIUM | 2 | 1 | | 2023-01-23 22:32 | [#199 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view) | MEDIUM | 2 | 1 | | 2023-01-16 22:39 | [#1813 句子相似性 III](https://leetcode.cn/problems/sentence-similarity-iii) | MEDIUM | 3 | 1 | @@ -44,20 +237,14 @@ | 2023-01-09 16:15 | [#1806 还原排列的最少操作步数](https://leetcode.cn/problems/minimum-number-of-operations-to-reinitialize-a-permutation) | MEDIUM | 1 | 1 | | 2023-01-07 22:33 | [#1658 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero) | MEDIUM | 1 | 1 | | 2023-01-05 20:00 | [#143 重排链表](https://leetcode.cn/problems/reorder-list) | MEDIUM | 2 | 1 | -| 2023-01-05 19:37 | [#206 反转链表](https://leetcode.cn/problems/reverse-linked-list) | EASY | 2 | 1 | | 2023-01-03 15:54 | [#34 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array) | MEDIUM | 4 | 1 | -| 2023-01-01 19:08 | [#215 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array) | MEDIUM | 2 | 1 | | 2022-12-31 10:58 | [#2037 使每位学生都有座位的最少移动次数](https://leetcode.cn/problems/minimum-number-of-moves-to-seat-everyone) | EASY | 1 | 1 | | 2022-12-19 14:07 | [#1971 寻找图中是否存在路径](https://leetcode.cn/problems/find-if-path-exists-in-graph) | EASY | 4 | 1 | -| 2022-12-18 21:25 | [#28 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string) | MEDIUM | 1 | 1 | +| 2022-12-18 21:25 | [#28 找出字符串中第一个匹配项的下标](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string) | EASY | 1 | 1 | | 2022-12-17 18:17 | [#1764 通过连接另一个数组的子数组得到一个数组](https://leetcode.cn/problems/form-array-by-concatenating-subarrays-of-another-array) | MEDIUM | 4 | 1 | | 2022-12-16 12:06 | [#1785 构成特定和需要添加的最少元素](https://leetcode.cn/problems/minimum-elements-to-add-to-form-a-given-sum) | MEDIUM | 2 | 1 | -| 2022-12-15 20:52 | [#21 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists) | EASY | 1 | 1 | -| 2022-12-15 20:42 | [#剑指 Offer 25 合并两个排序的链表](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof) | EASY | 1 | 1 | -| 2022-12-15 20:23 | [#23 合并K个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists) | HARD | 4 | 1 | -| 2022-12-15 13:46 | [#912 排序数组](https://leetcode.cn/problems/sort-an-array) | MEDIUM | 10 | **4** | +| 2022-12-15 20:42 | [#LCR 142 训练计划 IV](https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof) | EASY | 1 | 1 | | 2022-12-15 11:18 | [#1945 字符串转化后的各位数字之和](https://leetcode.cn/problems/sum-of-digits-of-string-after-convert) | EASY | 3 | 1 | -| 2022-12-13 12:34 | [#24 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs) | MEDIUM | 4 | 1 | | 2022-12-13 11:30 | [#1832 判断句子是否为全字母句](https://leetcode.cn/problems/check-if-the-sentence-is-pangram) | EASY | 1 | 1 | | 2022-12-12 16:15 | [#1781 所有子字符串美丽值之和](https://leetcode.cn/problems/sum-of-beauty-of-all-substrings) | MEDIUM | 1 | 1 | | 2022-12-11 16:13 | [#1827 最少操作使数组递增](https://leetcode.cn/problems/minimum-operations-to-make-the-array-increasing) | EASY | 1 | 1 | @@ -78,15 +265,12 @@ | 2022-11-26 18:56 | [#787 K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops) | MEDIUM | 6 | **2** | | 2022-11-26 16:01 | [#882 细分图中的可到达节点](https://leetcode.cn/problems/reachable-nodes-in-subdivided-graph) | HARD | 2 | 1 | | 2022-11-26 13:52 | [#809 情感丰富的文字](https://leetcode.cn/problems/expressive-words) | MEDIUM | 2 | 1 | -| 2022-11-24 16:25 | [#19 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list) | MEDIUM | 6 | **2** | | 2022-11-24 15:52 | [#1668 最大重复子字符串](https://leetcode.cn/problems/maximum-repeating-substring) | EASY | 4 | **2** | | 2022-11-24 15:03 | [#795 区间子数组个数](https://leetcode.cn/problems/number-of-subarrays-with-bounded-maximum) | MEDIUM | 3 | 1 | | 2022-11-23 21:31 | [#878 第 N 个神奇数字](https://leetcode.cn/problems/nth-magical-number) | HARD | 9 | **2** | | 2022-11-23 17:30 | [#1742 盒子中小球的最大数量](https://leetcode.cn/problems/maximum-number-of-balls-in-a-box) | EASY | 1 | 1 | -| 2022-11-21 20:32 | [#18 四数之和](https://leetcode.cn/problems/4sum) | MEDIUM | 4 | 1 | | 2022-11-21 19:47 | [#16 最接近的三数之和](https://leetcode.cn/problems/3sum-closest) | MEDIUM | 2 | 1 | | 2022-11-21 18:17 | [#1 两数之和](https://leetcode.cn/problems/two-sum) | EASY | 11 | **2** | -| 2022-11-21 16:43 | [#15 三数之和](https://leetcode.cn/problems/3sum) | MEDIUM | 5 | **2** | | 2022-11-21 14:35 | [#692 前K个高频单词](https://leetcode.cn/problems/top-k-frequent-words) | MEDIUM | 2 | 1 | | 2022-11-21 11:04 | [#799 香槟塔](https://leetcode.cn/problems/champagne-tower) | MEDIUM | 2 | 1 | | 2022-11-19 20:48 | [#139 单词拆分](https://leetcode.cn/problems/word-break) | MEDIUM | 3 | 1 | @@ -98,7 +282,7 @@ | 2022-11-15 19:11 | [#1710 卡车上的最大单元数](https://leetcode.cn/problems/maximum-units-on-a-truck) | EASY | 2 | 1 | | 2022-11-14 00:19 | [#704 二分查找](https://leetcode.cn/problems/binary-search) | EASY | 10 | **2** | | 2022-11-13 17:37 | [#916 单词子集](https://leetcode.cn/problems/word-subsets) | MEDIUM | 4 | 1 | -| 2022-11-13 15:18 | [#剑指 Offer 51 数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof) | HARD | 3 | **3** | +| 2022-11-13 15:18 | [#LCR 170 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof) | HARD | 3 | **3** | | 2022-11-13 13:33 | [#791 自定义字符串排序](https://leetcode.cn/problems/custom-sort-string) | MEDIUM | 2 | 1 | | 2022-11-12 20:54 | [#1339 分裂二叉树的最大乘积](https://leetcode.cn/problems/maximum-product-of-splitted-binary-tree) | MEDIUM | 3 | 1 | | 2022-11-12 14:18 | [#790 多米诺和托米诺平铺](https://leetcode.cn/problems/domino-and-tromino-tiling) | MEDIUM | 4 | 1 | @@ -112,7 +296,6 @@ | 2022-11-02 20:43 | [#1620 网络信号最好的坐标](https://leetcode.cn/problems/coordinate-with-maximum-network-quality) | MEDIUM | 1 | 1 | | 2022-11-01 14:49 | [#1662 检查两个字符串数组是否相等](https://leetcode.cn/problems/check-if-two-string-arrays-are-equivalent) | EASY | 1 | 1 | | 2022-10-31 10:25 | [#481 神奇字符串](https://leetcode.cn/problems/magical-string) | MEDIUM | 4 | 1 | -| 2022-10-30 20:27 | [#784 字母大小写全排列](https://leetcode.cn/problems/letter-case-permutation) | MEDIUM | 4 | **2** | | 2022-10-29 23:17 | [#1773 统计匹配检索规则的物品数量](https://leetcode.cn/problems/count-items-matching-a-rule) | EASY | 2 | 1 | | 2022-10-27 10:20 | [#1822 数组元素积的符号](https://leetcode.cn/problems/sign-of-the-product-of-an-array) | EASY | 2 | 1 | | 2022-10-24 15:37 | [#1235 规划兼职工作](https://leetcode.cn/problems/maximum-profit-in-job-scheduling) | HARD | 7 | **2** | @@ -144,7 +327,6 @@ | 2022-09-26 15:21 | [#788 旋转数字](https://leetcode.cn/problems/rotated-digits) | MEDIUM | 1 | 1 | | 2022-09-22 09:45 | [#1640 能否连接形成数组](https://leetcode.cn/problems/check-array-formation-through-concatenation) | EASY | 3 | 1 | | 2022-09-13 15:55 | [#1608 特殊数组的特征值](https://leetcode.cn/problems/special-array-with-x-elements-greater-than-or-equal-x) | EASY | 7 | 1 | -| 2022-09-13 15:06 | [#670 最大交换](https://leetcode.cn/problems/maximum-swap) | MEDIUM | 7 | 1 | | 2022-09-09 15:29 | [#1598 文件夹操作日志搜集器](https://leetcode.cn/problems/crawler-log-folder) | EASY | 3 | 1 | | 2022-09-01 13:48 | [#1475 商品折扣后的最终价格](https://leetcode.cn/problems/final-prices-with-a-special-discount-in-a-shop) | EASY | 1 | 1 | | 2022-08-31 13:22 | [#946 验证栈序列](https://leetcode.cn/problems/validate-stack-sequences) | MEDIUM | 2 | 1 | @@ -153,25 +335,22 @@ | 2022-08-10 18:34 | [#640 求解方程](https://leetcode.cn/problems/solve-the-equation) | MEDIUM | 4 | 1 | | 2022-08-01 12:42 | [#1374 生成每种字符都是奇数个的字符串](https://leetcode.cn/problems/generate-a-string-with-characters-that-have-odd-counts) | EASY | 1 | 1 | | 2022-07-25 14:34 | [#919 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter) | MEDIUM | 1 | 1 | -| 2022-07-23 14:13 | [#剑指 Offer II 115 重建序列](https://leetcode.cn/problems/ur2n8P) | MEDIUM | 7 | 1 | +| 2022-07-23 14:13 | [#LCR 115 序列重建](https://leetcode.cn/problems/ur2n8P) | MEDIUM | 7 | 1 | | 2022-07-17 11:09 | [#565 数组嵌套](https://leetcode.cn/problems/array-nesting) | MEDIUM | 1 | 1 | | 2022-07-14 13:12 | [#745 前缀和后缀搜索](https://leetcode.cn/problems/prefix-and-suffix-search) | HARD | 2 | 1 | -| 2022-07-13 19:22 | [#735 行星碰撞](https://leetcode.cn/problems/asteroid-collision) | MEDIUM | 11 | 1 | +| 2022-07-13 19:22 | [#735 小行星碰撞](https://leetcode.cn/problems/asteroid-collision) | MEDIUM | 11 | 1 | | 2022-07-11 13:05 | [#676 实现一个魔法字典](https://leetcode.cn/problems/implement-magic-dictionary) | MEDIUM | 2 | 1 | -| 2022-07-08 14:39 | [#3 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters) | MEDIUM | 5 | 1 | | 2022-07-08 13:51 | [#1217 玩筹码](https://leetcode.cn/problems/minimum-cost-to-move-chips-to-the-same-position) | EASY | 1 | 1 | | 2022-07-07 15:38 | [#648 单词替换](https://leetcode.cn/problems/replace-words) | MEDIUM | 3 | 1 | -| 2022-07-05 14:13 | [#307 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable) | MEDIUM | 3 | **2** | | 2022-07-05 11:23 | [#729 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i) | MEDIUM | 8 | 1 | | 2022-07-04 10:40 | [#1200 最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference) | EASY | 2 | 1 | | 2022-07-03 20:08 | [#556 下一个更大元素 III](https://leetcode.cn/problems/next-greater-element-iii) | MEDIUM | 1 | 1 | -| 2022-07-03 18:41 | [#31 下一个排列](https://leetcode.cn/problems/next-permutation) | MEDIUM | 3 | 1 | | 2022-06-24 13:38 | [#515 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row) | MEDIUM | 1 | 1 | | 2022-06-22 14:33 | [#513 找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value) | MEDIUM | 4 | 1 | | 2022-06-21 19:28 | [#1108 IP 地址无效化](https://leetcode.cn/problems/defanging-an-ip-address) | EASY | 2 | 1 | | 2022-06-19 23:06 | [#98 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree) | MEDIUM | 13 | 1 | | 2022-06-19 15:20 | [#508 出现次数最多的子树元素和](https://leetcode.cn/problems/most-frequent-subtree-sum) | MEDIUM | 2 | 1 | -| 2022-06-18 14:19 | [#剑指 Offer II 029 排序的循环链表](https://leetcode.cn/problems/4ueAj6) | MEDIUM | 15 | 1 | +| 2022-06-18 14:19 | [#LCR 029 循环有序列表的插入](https://leetcode.cn/problems/4ueAj6) | MEDIUM | 15 | 1 | | 2022-06-17 15:48 | [#1089 复写零](https://leetcode.cn/problems/duplicate-zeros) | EASY | 2 | 1 | | 2022-06-13 23:05 | [#1051 高度检查器](https://leetcode.cn/problems/height-checker) | EASY | 2 | 1 | | 2022-06-12 23:05 | [#890 查找和替换模式](https://leetcode.cn/problems/find-and-replace-pattern) | MEDIUM | 1 | 1 | @@ -188,7 +367,6 @@ | 2022-05-17 11:08 | [#953 验证外星语词典](https://leetcode.cn/problems/verifying-an-alien-dictionary) | EASY | 2 | 1 | | 2022-05-16 16:08 | [#面试题 04.06 后继者](https://leetcode.cn/problems/successor-lcci) | MEDIUM | 5 | 1 | | 2022-05-15 14:35 | [#812 最大三角形面积](https://leetcode.cn/problems/largest-triangle-area) | EASY | 4 | 1 | -| 2022-05-13 22:09 | [#102 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal) | MEDIUM | 3 | 1 | | 2022-05-13 13:28 | [#面试题 01.05 一次编辑](https://leetcode.cn/problems/one-away-lcci) | MEDIUM | 3 | 1 | | 2022-05-12 21:36 | [#944 删列造序](https://leetcode.cn/problems/delete-columns-to-make-sorted) | EASY | 2 | 1 | | 2022-05-10 15:12 | [#210 课程表 II](https://leetcode.cn/problems/course-schedule-ii) | MEDIUM | 2 | 1 | @@ -231,7 +409,6 @@ | 2022-04-06 14:13 | [#310 最小高度树](https://leetcode.cn/problems/minimum-height-trees) | MEDIUM | 1 | 1 | | 2022-04-05 15:42 | [#762 二进制表示中质数个计算置位](https://leetcode.cn/problems/prime-number-of-set-bits-in-binary-representation) | EASY | 3 | 1 | | 2022-04-03 20:29 | [#744 寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target) | EASY | 8 | 1 | -| 2022-04-02 20:52 | [#198 打家劫舍](https://leetcode.cn/problems/house-robber) | MEDIUM | 8 | **3** | | 2022-04-02 18:50 | [#132 分割回文串 II](https://leetcode.cn/problems/palindrome-partitioning-ii) | HARD | 4 | 1 | | 2022-04-01 11:41 | [#954 二倍数对数组](https://leetcode.cn/problems/array-of-doubled-pairs) | MEDIUM | 10 | 1 | | 2022-03-31 12:57 | [#728 自除数](https://leetcode.cn/problems/self-dividing-numbers) | EASY | 1 | 1 | @@ -246,7 +423,7 @@ | 2022-03-22 12:43 | [#2038 如果相邻两个颜色均相同则删除当前颜色](https://leetcode.cn/problems/remove-colored-pieces-if-both-neighbors-are-the-same-color) | MEDIUM | 3 | 1 | | 2022-03-21 15:27 | [#653 两数之和 IV - 输入二叉搜索树](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst) | EASY | 1 | 1 | | 2022-03-20 15:10 | [#2039 网络空闲的时刻](https://leetcode.cn/problems/the-time-when-the-network-becomes-idle) | MEDIUM | 1 | 1 | -| 2022-03-19 15:00 | [#606 根据二叉树创建字符串](https://leetcode.cn/problems/construct-string-from-binary-tree) | EASY | 3 | 1 | +| 2022-03-19 15:00 | [#606 根据二叉树创建字符串](https://leetcode.cn/problems/construct-string-from-binary-tree) | MEDIUM | 3 | 1 | | 2022-03-18 12:11 | [#2043 简易银行系统](https://leetcode.cn/problems/simple-bank-system) | MEDIUM | 1 | 1 | | 2022-03-17 14:57 | [#720 词典中最长的单词](https://leetcode.cn/problems/longest-word-in-dictionary) | MEDIUM | 8 | 1 | | 2022-03-16 19:29 | [#432 全 O(1) 的数据结构](https://leetcode.cn/problems/all-oone-data-structure) | HARD | 4 | 1 | @@ -258,33 +435,22 @@ | 2021-08-03 13:57 | [#581 最短无序连续子数组](https://leetcode.cn/problems/shortest-unsorted-continuous-subarray) | MEDIUM | 8 | 1 | | 2021-07-30 19:34 | [#171 Excel 表列序号](https://leetcode.cn/problems/excel-sheet-column-number) | EASY | 5 | 1 | | 2021-07-13 09:53 | [#40 组合总和 II](https://leetcode.cn/problems/combination-sum-ii) | MEDIUM | 3 | 1 | -| 2021-07-13 09:16 | [#39 组合总和](https://leetcode.cn/problems/combination-sum) | MEDIUM | 1 | 1 | | 2021-07-12 19:52 | [#714 买卖股票的最佳时机含手续费](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee) | MEDIUM | 1 | 1 | | 2021-07-12 18:59 | [#740 删除并获得点数](https://leetcode.cn/problems/delete-and-earn) | MEDIUM | 1 | 1 | -| 2021-07-07 14:10 | [#213 打家劫舍 II](https://leetcode.cn/problems/house-robber-ii) | MEDIUM | 3 | 1 | | 2021-07-06 16:21 | [#746 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs) | EASY | 1 | 1 | -| 2021-07-06 16:03 | [#70 爬楼梯](https://leetcode.cn/problems/climbing-stairs) | EASY | 2 | 1 | | 2021-07-05 15:44 | [#120 三角形最小路径和](https://leetcode.cn/problems/triangle) | MEDIUM | 2 | 1 | | 2021-07-05 14:12 | [#55 跳跃游戏](https://leetcode.cn/problems/jump-game) | MEDIUM | 4 | 1 | | 2021-07-05 13:41 | [#45 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii) | MEDIUM | 15 | 1 | -| 2021-07-05 13:03 | [#22 括号生成](https://leetcode.cn/problems/generate-parentheses) | MEDIUM | 2 | **2** | | 2021-07-05 12:31 | [#509 斐波那契数](https://leetcode.cn/problems/fibonacci-number) | EASY | 2 | 1 | -| 2021-07-05 12:30 | [#1137 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number) | EASY | 1 | 1 | | 2021-07-02 15:38 | [#1833 雪糕的最大数量](https://leetcode.cn/problems/maximum-ice-cream-bars) | MEDIUM | 4 | 1 | -| 2021-06-15 10:56 | [#33 搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array) | MEDIUM | 1 | 1 | | 2021-06-13 15:46 | [#278 第一个错误的版本](https://leetcode.cn/problems/first-bad-version) | EASY | 4 | 1 | -| 2021-06-07 19:49 | [#494 目标和](https://leetcode.cn/problems/target-sum) | MEDIUM | 1 | 1 | | 2021-06-04 09:26 | [#160 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists) | EASY | 7 | 1 | | 2021-05-24 16:52 | [#130 被围绕的区域](https://leetcode.cn/problems/surrounded-regions) | MEDIUM | 4 | **2** | | 2021-05-24 11:09 | [#547 省份数量](https://leetcode.cn/problems/number-of-provinces) | MEDIUM | 1 | 1 | -| 2021-05-22 00:06 | [#695 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island) | MEDIUM | 3 | 1 | -| 2021-05-21 14:21 | [#200 岛屿数量](https://leetcode.cn/problems/number-of-islands) | MEDIUM | 1 | 1 | | 2021-05-10 15:31 | [#872 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees) | EASY | 7 | 1 | | 2021-05-05 10:53 | [#154 寻找旋转排序数组中的最小值 II](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii) | HARD | 1 | 1 | -| 2021-05-05 10:49 | [#剑指 Offer 11 旋转数组的最小数字](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof) | EASY | 2 | **2** | +| 2021-05-05 10:49 | [#LCR 128 库存管理 I](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof) | EASY | 2 | **2** | | 2021-04-13 18:39 | [#783 二叉搜索树节点最小距离](https://leetcode.cn/problems/minimum-distance-between-bst-nodes) | EASY | 1 | 1 | -| 2021-03-30 15:03 | [#47 全排列 II](https://leetcode.cn/problems/permutations-ii) | MEDIUM | 1 | 1 | -| 2021-03-30 14:38 | [#46 全排列](https://leetcode.cn/problems/permutations) | MEDIUM | 3 | 1 | | 2021-03-30 13:37 | [#74 搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix) | MEDIUM | 1 | 1 | | 2021-03-27 22:24 | [#61 旋转链表](https://leetcode.cn/problems/rotate-list) | MEDIUM | 3 | 1 | | 2021-03-22 18:57 | [#191 位1的个数](https://leetcode.cn/problems/number-of-1-bits) | EASY | 1 | 1 | @@ -298,19 +464,13 @@ | 2020-08-10 14:09 | [#696 计数二进制子串](https://leetcode.cn/problems/count-binary-substrings) | EASY | 4 | 1 | | 2020-08-08 09:45 | [#99 恢复二叉搜索树](https://leetcode.cn/problems/recover-binary-search-tree) | MEDIUM | 1 | 1 | | 2020-08-06 16:44 | [#495 提莫攻击](https://leetcode.cn/problems/teemo-attacking) | EASY | 2 | 1 | -| 2020-08-05 16:41 | [#337 打家劫舍 III](https://leetcode.cn/problems/house-robber-iii) | MEDIUM | 2 | 1 | | 2020-08-03 20:36 | [#415 字符串相加](https://leetcode.cn/problems/add-strings) | EASY | 6 | 1 | -| 2020-08-02 19:45 | [#114 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list) | MEDIUM | 1 | 1 | | 2020-07-31 13:43 | [#面试题 17.01 不用加号的加法](https://leetcode.cn/problems/add-without-plus-lcci) | EASY | 1 | 1 | | 2020-07-31 13:05 | [#面试题 08.03 魔术索引](https://leetcode.cn/problems/magic-index-lcci) | EASY | 2 | 1 | | 2020-07-29 10:05 | [#12 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman) | MEDIUM | 1 | 1 | | 2020-07-28 18:48 | [#104 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree) | EASY | 1 | 1 | | 2020-07-27 10:32 | [#392 判断子序列](https://leetcode.cn/problems/is-subsequence) | EASY | 8 | 1 | -| 2020-07-23 15:55 | [#2 两数相加](https://leetcode.cn/problems/add-two-numbers) | MEDIUM | 5 | 1 | | 2020-07-23 14:37 | [#344 反转字符串](https://leetcode.cn/problems/reverse-string) | EASY | 5 | 1 | -| 2020-07-23 14:15 | [#64 最小路径和](https://leetcode.cn/problems/minimum-path-sum) | MEDIUM | 6 | 1 | -| 2020-07-22 21:24 | [#11 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water) | MEDIUM | 4 | 1 | | 2020-07-20 14:17 | [#167 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted) | MEDIUM | 1 | 1 | | 2020-07-18 23:56 | [#97 交错字符串](https://leetcode.cn/problems/interleaving-string) | MEDIUM | 4 | 1 | | 2020-07-17 16:01 | [#35 搜索插入位置](https://leetcode.cn/problems/search-insert-position) | EASY | 7 | 1 | -| 2020-04-22 22:11 | [#17 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number) | MEDIUM | 3 | 1 | diff --git "a/Simulation/1144. \351\200\222\345\207\217\345\205\203\347\264\240\344\275\277\346\225\260\347\273\204\345\221\210\351\224\257\351\275\277\347\212\266.md" "b/Simulation/1144. \351\200\222\345\207\217\345\205\203\347\264\240\344\275\277\346\225\260\347\273\204\345\221\210\351\224\257\351\275\277\347\212\266.md" new file mode 100644 index 0000000..e9df5f6 --- /dev/null +++ "b/Simulation/1144. \351\200\222\345\207\217\345\205\203\347\264\240\344\275\277\346\225\260\347\273\204\345\221\210\351\224\257\351\275\277\347\212\266.md" @@ -0,0 +1,59 @@ +#### 1144. 递减元素使数组呈锯齿状 + +难度:中等 + +--- + +给你一个整数数组 `nums`,每次 **操作**  会从中选择一个元素并 **将该元素的值减少 1** 。 + +如果符合下列情况之一,则数组 `A` 就是 **锯齿数组** : + +* 每个偶数索引对应的元素都大于相邻的元素,即 `A[0] > A[1] < A[2] > A[3] < A[4] > ...` +* 或者,每个奇数索引对应的元素都大于相邻的元素,即 `A[0] < A[1] > A[2] < A[3] > A[4] < ...` + +返回将数组 `nums` 转换为锯齿数组所需的最小操作次数。 + + **示例 1:** + +``` +输入:nums = [1,2,3] +输出:2 +解释:我们可以把 2 递减到 0,或把 3 递减到 1。 +``` + + **示例 2:** + +``` +输入:nums = [9,6,1,6,2] +输出:4 +``` + + **提示:** + +* `1 <= nums.length <= 1000` +* `1 <= nums[i] <= 1000` + +--- + +模拟: + +奇数和偶数两种情况分别讨论,其调用的方法都是同一个,用一个 `temp` 临时变量记录差值,取与前者和与后者的差值的最大值。 + +```java +class Solution { +    public int movesToMakeZigzag(int[] nums) { +        return Math.min(method(nums, true), method(nums, false)); +    } + +    private int method(int[] nums, boolean isOdd){ +        int start = isOdd == true ? 1 : 0, ans = 0; +        for(int i = start; i < nums.length; i += 2){ +            int temp = 0; +            if(i - 1 >= 0) temp = Math.max(temp, nums[i] - nums[i - 1] + 1); +            if(i + 1 < nums.length) temp = Math.max(temp, nums[i] - nums[i + 1] + 1); +            ans += temp;  +        } +        return ans; +    } +} +``` \ No newline at end of file diff --git "a/Simulation/1419. \346\225\260\351\235\222\350\233\231.md" "b/Simulation/1419. \346\225\260\351\235\222\350\233\231.md" new file mode 100644 index 0000000..34f909f --- /dev/null +++ "b/Simulation/1419. \346\225\260\351\235\222\350\233\231.md" @@ -0,0 +1,76 @@ +#### 1419. 数青蛙 + +难度:中等 + +--- + +给你一个字符串 `croakOfFrogs`,它表示不同青蛙发出的蛙鸣声(字符串 `"croak"` )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 `croakOfFrogs` 中会混合多个 `“croak”` _。_ + +请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。 + +要想发出蛙鸣 "croak",青蛙必须 **依序** 输出 `‘c’, ’r’, ’o’, ’a’, ’k’` 这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。如果字符串 `croakOfFrogs` 不是由若干有效的 "croak" 字符混合而成,请返回 `-1` 。 + + **示例 1:** + +``` +输入:croakOfFrogs = "croakcroak" +输出:1 +解释:一只青蛙 “呱呱” 两次 +``` + + **示例 2:** + +``` +输入:croakOfFrogs = "crcoakroak" +输出:2 +解释:最少需要两只青蛙,“呱呱” 声用黑体标注 +第一只青蛙 "crcoakroak" +第二只青蛙 "crcoakroak" +``` + + **示例 3:** + +``` +输入:croakOfFrogs = "croakcrook" +输出:-1 +解释:给出的字符串不是 "croak" 的有效组合。 +``` + + **提示:** + +* `1 <= croakOfFrogs.length <= 10^5` +* 字符串中的字符只有 `'c'`, `'r'`, `'o'`, `'a'` 或者 `'k'` + +--- + +模拟: + +方法比较巧妙,遍历字符串,如果当前字符为 `c`,意味着当前青蛙数加一;如果为 `k`,则当前青蛙数减一;其他只需要考虑字符串不是由若干有效的 `croak` 字符混合而成的情况即可。 + +```Java +class Solution { + public int minNumberOfFrogs(String croakOfFrogs) { + int n = croakOfFrogs.length(); + if(n % 5 != 0) return -1; + int[] temp = new int[5]; + int ans = 0, cur_num = 0; + for(int i = 0; i < n; i++){ + char c = croakOfFrogs.charAt(i); + int index = 0; + if(c == 'r') index = 1; + else if(c == 'o') index = 2; + else if(c == 'a') index = 3; + else if(c == 'k') index = 4; + temp[index]++; + if(index == 0){ + cur_num++; + ans = Math.max(ans, cur_num); + } + else if(index == 4) cur_num--; + if(index != 0 && temp[index] > temp[index - 1]) return -1; + } + for(int i = 0; i < 5; i++) if(temp[i] != n / 5) return -1; + return ans; + } +} +``` \ No newline at end of file diff --git "a/Simulation/289. \347\224\237\345\221\275\346\270\270\346\210\217.md" "b/Simulation/289. \347\224\237\345\221\275\346\270\270\346\210\217.md" new file mode 100644 index 0000000..864e6c9 --- /dev/null +++ "b/Simulation/289. \347\224\237\345\221\275\346\270\270\346\210\217.md" @@ -0,0 +1,88 @@ +#### 289. 生命游戏 + +难度:中等 + +--- + +根据 [百度百科](https://baike.baidu.com/item/%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F/2926434?fr=aladdin) ,  **生命游戏**  ,简称为 **生命** ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。 + +给定一个包含 `m × n` 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: `1` 即为 **活细胞** (live),或 `0` 即为 **死细胞** (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律: + +1. 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡; +2. 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活; +3. 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡; +4. 如果死细胞周围正好有三个活细胞,则该位置死细胞复活; + +下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 `m x n` 网格面板 `board` 的当前状态,返回下一个状态。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2020/12/26/grid1.jpg) +``` +输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]] +输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2020/12/26/grid2.jpg) +``` +输入:board = [[1,1],[1,0]] +输出:[[1,1],[1,1]] +``` + + **提示:** + +* `m == board.length` +* `n == board[i].length` +* `1 <= m, n <= 25` +* `board[i][j]` 为 `0` 或 `1` + + **进阶:** + +* 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。 +* 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题? + +--- + +模拟: + +原地变换。 + +- 如果原来是活细胞,后来是死细胞,则赋值为 `2`。 + +- 如果原来是死细胞,后来是活细胞,则赋值为 `-1`。 + +遍历时只要其值大于零,原来就是活细胞。 + +```Java +class Solution { + public void gameOfLife(int[][] board) { + int m = board.length, n = board[0].length; + int[] bias = {-1, 0, 1}; + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + int alive = 0; + for(int x = 0; x < 3; x++){ + for(int y = 0; y < 3; y++){ + if(bias[x] != 0 || bias[y] != 0){ + int cur_i = i + bias[x], cur_j = j + bias[y]; + if((cur_i >= 0 && cur_i < m) && (cur_j >= 0 && cur_j < n) && board[cur_i][cur_j] > 0){ + alive++; + } + } + } + } + if(board[i][j] == 0 && alive == 3) board[i][j] = -1; + if(board[i][j] == 1 && (alive < 2 || alive > 3)) board[i][j] = 2; + } + } + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + if(board[i][j] == 2) board[i][j] = 0; + else if(board[i][j] == -1) board[i][j] = 1; + } + } + } +} +``` \ No newline at end of file diff --git "a/Simulation/43. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" "b/Simulation/43. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" new file mode 100644 index 0000000..88ac2e3 --- /dev/null +++ "b/Simulation/43. \345\255\227\347\254\246\344\270\262\347\233\270\344\271\230.md" @@ -0,0 +1,63 @@ +#### 43. 字符串相乘 + +难度:中等 + +--- + +给定两个以字符串形式表示的非负整数 `num1` 和 `num2`,返回 `num1` 和 `num2` 的乘积,它们的乘积也表示为字符串形式。 + + **注意:** 不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 + + **示例 1:** + +``` +输入: num1 = "2", num2 = "3" +输出: "6" +``` + + **示例 2:** + +``` +输入: num1 = "123", num2 = "456" +输出: "56088" +``` + + **提示:** + +* `1 <= num1.length, num2.length <= 200` +* `num1` 和 `num2` 只能由数字组成。 +* `num1` 和 `num2` 都不包含任何前导零,除了数字0本身。 + +--- + +模拟: + +```Java +func multiply(num1 string, num2 string) string { + if num1 == "0" || num2 == "0" { + return "0" + } + m, n := len(num1), len(num2) + array := make([]int, m+n) // 两数相乘,最大长度为 m+n + for i := m - 1; i >= 0; i-- { + temp1 := int(num1[i] - '0') + for j := n - 1; j >= 0; j-- { + temp2 := int(num2[j] - '0') + array[i+j+1] += temp1 * temp2 // 存储的是乘积,没向前进位 + } + } + // 处理进位,i的进位位置在i-1位置 + for i := len(array) - 1; i > 0; i-- { + array[i-1] += array[i] / 10 + array[i] %= 10 + } + res := "" + for i := range array { + if i == 0 && array[i] == 0 { // 如果第0位为0,则需要处理 + continue + } + res += string(array[i] + '0') + } + return res +} +``` \ No newline at end of file diff --git "a/Simulation/54. \350\236\272\346\227\213\347\237\251\351\230\265.md" "b/Simulation/54. \350\236\272\346\227\213\347\237\251\351\230\265.md" new file mode 100644 index 0000000..eb006b2 --- /dev/null +++ "b/Simulation/54. \350\236\272\346\227\213\347\237\251\351\230\265.md" @@ -0,0 +1,82 @@ +#### 54. 螺旋矩阵 + +难度:中等 + +--- + +给你一个 `m` 行 `n` 列的矩阵 `matrix` ,请按照 **顺时针螺旋顺序** ,返回矩阵中的所有元素。 + + **示例 1:** + +![](https://assets.leetcode.com/uploads/2020/11/13/spiral1.jpg) +``` +输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] +输出:[1,2,3,6,9,8,7,4,5] +``` + + **示例 2:** + +![](https://assets.leetcode.com/uploads/2020/11/13/spiral.jpg) +``` +输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] +输出:[1,2,3,4,8,12,11,10,9,5,6,7] +``` + + **提示:** + +* `m == matrix.length` +* `n == matrix[i].length` +* `1 <= m, n <= 10` +* `-100 <= matrix[i][j] <= 100` + +--- + +模拟: + +用若干个变量分别表示下一步走的方向,上下左右四个方向的边界以及当前点的坐标。再分别考虑当前点到达角落时后续的移动情况。 + +```Go +func spiralOrder(matrix [][]int) []int { + left, right, up, down := 0, len(matrix[0]) - 1, 0, len(matrix) - 1 + first, second := 0, 0 + flag, sum := 1, (right + 1) * (down + 1) + res := make([]int, sum) + for cnt := 0; cnt < sum; cnt++ { + res[cnt] = matrix[first][second] + if flag == 1 { + if second == right{ + flag = 2 + up++ + first++ + }else { + second++ + } + } else if flag == 2 { + if first == down { + flag = 3 + right-- + second-- + } else { + first++ + } + } else if flag == 3 { + if second == left { + flag = 4 + down-- + first-- + } else { + second-- + } + } else if flag == 4 { + if first == up { + flag = 1 + left++ + second++ + } else { + first-- + } + } + } + return res +} +``` \ No newline at end of file diff --git "a/Sliding Window/1234. \346\233\277\346\215\242\345\255\220\344\270\262\345\276\227\345\210\260\345\271\263\350\241\241\345\255\227\347\254\246\344\270\262.md" "b/Sliding Window/1234. \346\233\277\346\215\242\345\255\220\344\270\262\345\276\227\345\210\260\345\271\263\350\241\241\345\255\227\347\254\246\344\270\262.md" new file mode 100644 index 0000000..c015167 --- /dev/null +++ "b/Sliding Window/1234. \346\233\277\346\215\242\345\255\220\344\270\262\345\276\227\345\210\260\345\271\263\350\241\241\345\255\227\347\254\246\344\270\262.md" @@ -0,0 +1,96 @@ +#### 1234. 替换子串得到平衡字符串 + +难度:中等 + +--- + +有一个只含有 `'Q', 'W', 'E', 'R'` 四种字符,且长度为 `n` 的字符串。 + +假如在该字符串中,这四个字符都恰好出现 `n/4` 次,那么它就是一个「平衡字符串」。 + +给你一个这样的字符串 `s`,请通过「替换一个子串」的方式,使原字符串 `s` 变成一个「平衡字符串」。 + +你可以用和「待替换子串」长度相同的  **任何** 其他字符串来完成替换。 + +请返回待替换子串的最小可能长度。 + +如果原字符串自身就是一个平衡字符串,则返回 `0`。 + + **示例 1:** + +``` +输入:s = "QWER" +输出:0 +解释:s 已经是平衡的了。 +``` + + **示例 2:** + +``` +输入:s = "QQWE" +输出:1 +解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。 +``` + + **示例 3:** + +``` +输入:s = "QQQW" +输出:2 +解释:我们可以把前面的 "QQ" 替换成 "ER"。 +``` + + **示例 4:** + +``` +输入:s = "QQQQ" +输出:3 +解释:我们可以替换后 3 个 'Q',使 s = "QWER"。 +``` + + **提示:** + +* `1 <= s.length <= 10^5` +* `s.length` 是 `4` 的倍数 +* `s` 中只含有 `'Q'`, `'W'`, `'E'`, `'R'` 四种字符 + +--- + +滑动窗口: + +首先明白一点,直接替换原子串也是可以得到平衡字符串的。 + +滑动窗口范围为 `[left, right]`,搜索**待替换子串**的步骤: + +1. 移动右边界扩大范围,使其范围外的子串尽可能合法 +2. 如果窗口外的子串是“合法”子串,移动左边界缩小范围,使其尽可能缩小待替换子串的长度 + +此处的“不合法”定义为**存在一个字符出现的次数大于 `n/4`**。相反,“合法”为**所有字符出现的次数均小于或等于 `n/4`**。并且需要特殊判断原子串是否是平衡子串。 + +```Java +class Solution { + public int balancedString(String s) { + int n = s.length(), m = n >> 2, ans = n; + int[] count = new int['Z']; + for(int i = 0; i < n; i++){ + count[s.charAt(i)]++; + } + if(check(count, m)) return 0; + int left = 0, right = 0; + while(right < n){ + count[s.charAt(right)]--; + while(check(count, m)){ + ans = Math.min(ans, right - left + 1); + count[s.charAt(left++)]++; + } + right++; + } + return ans; + } + + public boolean check(int[] count, int m){ + return count['Q'] <= m && count['W'] <= m && count['E'] <= m && count['R'] <= m; + } + +} +``` \ No newline at end of file diff --git "a/Sliding Window/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" "b/Sliding Window/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" new file mode 100644 index 0000000..dc86ac5 --- /dev/null +++ "b/Sliding Window/1423. \345\217\257\350\216\267\345\276\227\347\232\204\346\234\200\345\244\247\347\202\271\346\225\260.md" @@ -0,0 +1,87 @@ +#### 1423. 可获得的最大点数 + +难度:中等 + +--- + +几张卡牌 **排成一行** ,每张卡牌都有一个对应的点数。点数由整数数组 `cardPoints` 给出。 + +每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 `k` 张卡牌。 + +你的点数就是你拿到手中的所有卡牌的点数之和。 + +给你一个整数数组 `cardPoints` 和整数 `k`,请你返回可以获得的最大点数。 + + **示例 1:** + +``` +输入:cardPoints = [1,2,3,4,5,6,1], k = 3 +输出:12 +解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。 +``` + + **示例 2:** + +``` +输入:cardPoints = [2,2,2], k = 2 +输出:4 +解释:无论你拿起哪两张卡牌,可获得的点数总是 4 。 +``` + + **示例 3:** + +``` +输入:cardPoints = [9,7,7,9,7,7,9], k = 7 +输出:55 +解释:你必须拿起所有卡牌,可以获得的点数为所有卡牌的点数之和。 +``` + + **示例 4:** + +``` +输入:cardPoints = [1,1000,1], k = 1 +输出:1 +解释:你无法拿到中间那张卡牌,所以可以获得的最大点数为 1 。 +``` + + **示例 5:** + +``` +输入:cardPoints = [1,79,80,1,1,1,200,1], k = 3 +输出:202 +``` + + **提示:** + +* `1 <= cardPoints.length <= 10^5` +* `1 <= cardPoints[i] <= 10^4` +* `1 <= k <= cardPoints.length` + +--- + +滑动窗口: + +两种做法,第一种是求中间**连续的** `n-k` 张卡牌的最小值;第二种是扩展原数组使其头尾拼接,求中间**连续** `k` 张卡牌的最大值。 + +无论是哪种做法,都需要求滑动窗口的最值(最小或最大)。**滑动窗口在移动时,每向右移动一格,增加从右侧进入窗口的元素值,并减少从左侧离开窗口的元素值。** + +```Java +class Solution { + public int maxScore(int[] cardPoints, int k) { + int n = cardPoints.length; + int[] temp = new int[2 * n]; + System.arraycopy(cardPoints, 0, temp, 0, n); + System.arraycopy(cardPoints, 0, temp, n, n); + int cur = 0; + for(int i = 0; i < k; i++){ + cur += temp[n - k + i]; + } + int res = cur; + for(int i = 0; i < k; i++){ + cur += temp[n + i] - temp[n - k + i]; + res = Math.max(res, cur); + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Sliding Window/1838. \346\234\200\351\253\230\351\242\221\345\205\203\347\264\240\347\232\204\351\242\221\346\225\260.md" "b/Sliding Window/1838. \346\234\200\351\253\230\351\242\221\345\205\203\347\264\240\347\232\204\351\242\221\346\225\260.md" new file mode 100644 index 0000000..0be0586 --- /dev/null +++ "b/Sliding Window/1838. \346\234\200\351\253\230\351\242\221\345\205\203\347\264\240\347\232\204\351\242\221\346\225\260.md" @@ -0,0 +1,73 @@ +#### 1838. 最高频元素的频数 + +难度:中等 + +--- + +元素的 **频数** 是该元素在一个数组中出现的次数。 + +给你一个整数数组 `nums` 和一个整数 `k` 。在一步操作中,你可以选择 `nums` 的一个下标,并将该下标对应元素的值增加 `1` 。 + +执行最多 `k` 次操作后,返回数组中最高频元素的 **最大可能频数** _。_ + + **示例 1:** + +``` +输入:nums = [1,2,4], k = 5 +输出:3 +解释:对第一个元素执行 3 次递增操作,对第二个元素执 2 次递增操作,此时 nums = [4,4,4] 。 +4 是数组中最高频元素,频数是 3 。 +``` + + **示例 2:** + +``` +输入:nums = [1,4,8,13], k = 5 +输出:2 +解释:存在多种最优解决方案: +- 对第一个元素执行 3 次递增操作,此时 nums = [4,4,8,13] 。4 是数组中最高频元素,频数是 2 。 +- 对第二个元素执行 4 次递增操作,此时 nums = [1,8,8,13] 。8 是数组中最高频元素,频数是 2 。 +- 对第三个元素执行 5 次递增操作,此时 nums = [1,4,13,13] 。13 是数组中最高频元素,频数是 2 。 +``` + + **示例 3:** + +``` +输入:nums = [3,9,6], k = 2 +输出:1 +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `1 <= nums[i] <= 10^5` +* `1 <= k <= 10^5` + +--- + +滑动窗口: + +题目类似于补全两个下标之间的数以到达同一水平线,窗口内记录补全需要的总数,如果总数大于 `k`,则左下标向右移动,否则右下标继续向右移动。 + +```Go +func maxFrequency(nums []int, k int) int { + sort.Ints(nums) + ans := 1 + for left, right, total := 0, 1, 0; right < len(nums); right ++ { + total += (nums[right] - nums[right -1]) * (right - left) + if total > k { + total -= nums[right] - nums[left] + left++ + } + ans = max(ans, right - left + 1) + } + return ans +} + +func max(num1, num2 int) int { + if num1 > num2 { + return num1 + } + return num2 +} +``` \ No newline at end of file diff --git "a/Sliding Window/187. \351\207\215\345\244\215\347\232\204DNA\345\272\217\345\210\227.md" "b/Sliding Window/187. \351\207\215\345\244\215\347\232\204DNA\345\272\217\345\210\227.md" new file mode 100644 index 0000000..a161c5c --- /dev/null +++ "b/Sliding Window/187. \351\207\215\345\244\215\347\232\204DNA\345\272\217\345\210\227.md" @@ -0,0 +1,52 @@ +#### 187. 重复的DNA序列 + +难度:中等 + +--- + + **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 <= 10^5` +* `s[i]``==``'A'`、`'C'`、`'G'` or `'T'` + +--- +哈希表 + 固定窗口的滑动: + +固定窗口内的字符作为哈希表的 key, value 为出现的次数。次数等于 2 则加入到 res 里。 + +```Go +func findRepeatedDnaSequences(s string) []string { + var res []string + hashmap := make(map[string]int) + for i := 0; i <= len(s) - 10; i++ { + sub := s[i:i+10] + hashmap[sub]++ + if hashmap[sub] == 2 { + res = append(res, sub) + } + } + return res +} +``` \ No newline at end of file diff --git "a/Sliding Window/209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" "b/Sliding Window/209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" new file mode 100644 index 0000000..bd4b0ce --- /dev/null +++ "b/Sliding Window/209. \351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.md" @@ -0,0 +1,88 @@ +#### 209. 长度最小的子数组 + +难度:中等 + +--- + +给定一个含有 `n` 个正整数的数组和一个正整数 `target` **。** + +找出该数组中满足其总和大于等于 `target` 的长度最小的 **连续子数组**  `[numsl, numsl+1, ..., numsr-1, numsr]` ,并返回其长度 **。** 如果不存在符合条件的子数组,返回 `0` 。 + + **示例 1:** + +``` +输入:target = 7, nums = [2,3,1,2,4,3] +输出:2 +解释:子数组 [4,3] 是该条件下的长度最小的子数组。 +``` + + **示例 2:** + +``` +输入:target = 4, nums = [1,4,4] +输出:1 +``` + + **示例 3:** + +``` +输入:target = 11, nums = [1,1,1,1,1,1,1,1] +输出:0 +``` + + **提示:** + +* `1 <= target <= 10^9` +* `1 <= nums.length <= 10^5` +* `1 <= nums[i] <= 10^5` + + **进阶:** + +* 如果你已经实现 `O(n)` 时间复杂度的解法, 请尝试设计一个 `O(n log(n))` 时间复杂度的解法。 + +--- + +滑动窗口: + +滑动窗口也是一个特殊的**双指针**方法。滑动窗口的左右边界都从零开始,直到右边界到达数组最右侧为止。窗口内的子数组和大于等于 `target` 则循环向右移动左边界,否则向右移动右边界。 + +```Java +class Solution { + public int minSubArrayLen(int target, int[] nums) { + int n = nums.length, left = 0, right = 0, sum = 0, res = Integer.MAX_VALUE; + while(right < n){ + sum += nums[right]; + while(sum >= target){ + res = Math.min(res, right - left + 1); + sum -= nums[left++]; + } + right++; + } + return res == Integer.MAX_VALUE ? 0 : res; + } +} +``` + + + +Go: + +```go +func minSubArrayLen(target int, nums []int) int { + left, right, res, sum := 0, 0, math.MaxInt, 0 + for right < len(nums) { + sum += nums[right] + for sum >= target { + res = min(res, right - left + 1) + sum -= nums[left] + left++ + } + right++ + } + if res == math.MaxInt { + return 0 + } + return res +} +``` + diff --git "a/Sliding Window/2779. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\347\276\216\344\270\275\345\200\274.md" "b/Sliding Window/2779. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\347\276\216\344\270\275\345\200\274.md" new file mode 100644 index 0000000..c18deac --- /dev/null +++ "b/Sliding Window/2779. \346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\347\276\216\344\270\275\345\200\274.md" @@ -0,0 +1,69 @@ +#### 2779. 数组的最大美丽值 + +难度:中等 + +--- + +给你一个下标从 **0** 开始的整数数组 `nums` 和一个 **非负** 整数 `k` 。 + +在一步操作中,你可以执行下述指令: + +* 在范围 `[0, nums.length - 1]` 中选择一个 **此前没有选过** 的下标 `i` 。 +* 将 `nums[i]` 替换为范围 `[nums[i] - k, nums[i] + k]` 内的任一整数。 + +数组的 **美丽值** 定义为数组中由相等元素组成的最长子序列的长度。 + +对数组 `nums` 执行上述操作任意次后,返回数组可能取得的 **最大** 美丽值。 + + **注意:** 你 **只** 能对每个下标执行 **一次** 此操作。 + +数组的 **子序列** 定义是:经由原数组删除一些元素(也可能不删除)得到的一个新数组,且在此过程中剩余元素的顺序不发生改变。 + + **示例 1:** + +``` +输入:nums = [4,6,1,2], k = 2 +输出:3 +解释:在这个示例中,我们执行下述操作: +- 选择下标 1 ,将其替换为 4(从范围 [4,8] 中选出),此时 nums = [4,4,1,2] 。 +- 选择下标 3 ,将其替换为 4(从范围 [0,4] 中选出),此时 nums = [4,4,1,4] 。 +执行上述操作后,数组的美丽值是 3(子序列由下标 0 、1 、3 对应的元素组成)。 +可以证明 3 是我们可以得到的由相等元素组成的最长子序列长度。 +``` + + **示例 2:** + +``` +输入:nums = [1,1,1,1], k = 10 +输出:4 +解释:在这个示例中,我们无需执行任何操作。 +数组 nums 的美丽值是 4(整个数组)。 +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `0 <= nums[i], k <= 10^5` + +--- + +排序 + 滑动窗口: + +根据题目对子序列的定义,我们知道元素的顺序对数组的美丽值无影响,因此考虑先排序。 + +初始化窗口长度为一,并枚举右端点(右端点不断的向右移动),当右端点的下一位数与左端点的差大于两倍的 `k`,左端点向右移动一位。 + +这样能保证窗口长度只增不减,一定是最大美丽值。 + +```Go +func maximumBeauty(nums []int, k int) int { + sort.Ints(nums) + left, right := 0, 0 + for ;right < len(nums) - 1; right++ { + if nums[right + 1] - nums[left] > 2 * k { + left++ + } + } + return right - left + 1 +} +``` \ No newline at end of file diff --git "a/Sliding Window/3. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" "b/Sliding Window/3. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" new file mode 100644 index 0000000..55517f8 --- /dev/null +++ "b/Sliding Window/3. \346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.md" @@ -0,0 +1,59 @@ +#### 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 * 10^4` +* `s` 由英文字母、数字、符号和空格组成 + +--- + +滑动窗口 + 哈希表: + +使用哈希表记录当前字符最后一次出现的下标,并且用一个变量记录遍历过程中最后一次出现下标的最大值,最后再将当前下标与该最大值取差值。 + +```Java +class Solution { + public int lengthOfLongestSubstring(String s) { + HashMap hashmap = new HashMap<>(); + int res = 0, left = -1; + for(int i = 0; i < s.length(); i++){ + char c = s.charAt(i); + left = Math.max(left, hashmap.getOrDefault(c, -1)); + hashmap.put(c, i); + res = Math.max(res, i - left); + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Sliding Window/438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" "b/Sliding Window/438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" new file mode 100644 index 0000000..f30c60b --- /dev/null +++ "b/Sliding Window/438. \346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.md" @@ -0,0 +1,68 @@ +#### 438. 找到字符串中所有字母异位词 + +难度:中等 + +--- + +给定两个字符串 `s` 和 `p`,找到 `s` 中所有 `p` 的  **异位词**  的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 + + **异位词** 指由相同字母重排列形成的字符串(包括相同的字符串)。 + + **示例 1:** + +``` +输入: s = "cbaebabacd", p = "abc" +输出: [0,6] +解释: +起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 +起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。 +``` + +  **示例 2:** + +``` +输入: s = "abab", p = "ab" +输出: [0,1,2] +解释: +起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。 +起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。 +起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。 +``` + + **提示:** + +* `1 <= s.length, p.length <= 3 * 10^4` +* `s` 和 `p` 仅包含小写字母 + +--- + +滑动窗口: + +注意审题,在 `s` 中找所有 `p` 的子串。还有 `Arrays.equals(array1, array2)` 这个方法的使用来判断两个数组是否相同,不过也可以用数组遍历的方式替代。 + +以 `p` 为固定长度的数组在字符串 `s` 中进行滑动,每次滑动都进行相应的加减操作。 + +```Java +class Solution { + public List findAnagrams(String s, String p) { + if(s.length() < p.length()) return new ArrayList(); + int[] sCount = new int[26], pCount = new int[26]; + List ans = new ArrayList<>(); + for(int i = 0; i < p.length(); i++){ + sCount[s.charAt(i) - 'a']++; + pCount[p.charAt(i) - 'a']++; + } + if(Arrays.equals(sCount, pCount)){ + ans.add(0); + } + for(int i = 0; i < s.length() - p.length(); i++){ // 滑动 + sCount[s.charAt(i) - 'a']--; + sCount[s.charAt(i + p.length()) - 'a']++; + if(Arrays.equals(sCount, pCount)){ + ans.add(i + 1); + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Sliding Window/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" "b/Sliding Window/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" new file mode 100644 index 0000000..ccf0864 --- /dev/null +++ "b/Sliding Window/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" @@ -0,0 +1,55 @@ +#### 567. 字符串的排列 + +难度:中等 + +--- + +给你两个字符串 `s1` 和 `s2` ,写一个函数来判断 `s2` 是否包含 `s1` 的排列。如果是,返回 `true` ;否则,返回 `false` 。 + +换句话说,`s1` 的排列之一是 `s2` 的 **子串** 。 + + **示例 1:** + +``` +输入:s1 = "ab" s2 = "eidbaooo" +输出:true +解释:s2 包含 s1 的排列之一 ("ba"). +``` + + **示例 2:** + +``` +输入:s1= "ab" s2 = "eidboaoo" +输出:false +``` + + **提示:** + +* `1 <= s1.length, s2.length <= 10^4` +* `s1` 和 `s2` 仅包含小写字母 + +--- + +滑动窗口(固定窗口大小): + +以 `s1` 字符串长度为固定窗口大小,在 `s2` 字符串中滑动。用两个数组记录两个字符串中字母出现的次数,当两数组的计数器完全相同时,返回 `true`,否则遍历结束后返回 `false`。 + +```Java +class Solution { + public boolean checkInclusion(String s1, String s2) { + if(s1.length() > s2.length()) return false; + int[] cnt1 = new int[26], cnt2 = new int[26]; + for(int i = 0; i < s1.length(); i++){ + cnt1[s1.charAt(i) - 'a']++; + cnt2[s2.charAt(i) - 'a']++; + } + if(Arrays.equals(cnt1, cnt2)) return true; + for(int i = s1.length(); i < s2.length(); i++){ + cnt2[s2.charAt(i) - 'a']++; + cnt2[s2.charAt(i - s1.length()) - 'a']--; + if(Arrays.equals(cnt1, cnt2)) return true; + } + return false; + } +} +``` \ No newline at end of file diff --git "a/Sliding Window/76. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" "b/Sliding Window/76. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" new file mode 100644 index 0000000..abf3238 --- /dev/null +++ "b/Sliding Window/76. \346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.md" @@ -0,0 +1,88 @@ +#### 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 <= 10^5` +* `s` 和 `t` 由英文字母组成 + + **进阶:** 你能设计一个在 `o(m+n)` 时间内解决此问题的算法吗? + +--- + +滑动窗口: + +每次循环移动右移一次右指针,移动的过程中根据字符是否完全覆盖的情况循环右移左指针。第二个循环的过程中记录窗口长度是否为最小值。 + +```Go +func minWindow(s string, t string) string { + alphabet, count := map[byte]int{}, map[byte]int{} + for i := range t { + alphabet[t[i]]++ + } + ansLeft, ansRight := -1, -1 + ansLen := math.MaxInt32 + for left, right := 0, 0; right < len(s); right++ { + count[s[right]]++ + for check(alphabet, count) && left <= right { + if right - left + 1 < ansLen { + ansLen = right - left + 1 + ansLeft = left + ansRight = right + 1 + } + count[s[left]]-- + left++ + } + } + if ansLeft == -1 { + return "" + } + return s[ansLeft:ansRight] +} + +func check(alphabet, count map[byte]int) bool { + for k, v := range alphabet { + if count[k] < v { + return false + } + } + return true +} +``` \ No newline at end of file diff --git "a/Sort/1637. \344\270\244\347\202\271\344\271\213\351\227\264\344\270\215\345\214\205\345\220\253\344\273\273\344\275\225\347\202\271\347\232\204\346\234\200\345\256\275\345\236\202\347\233\264\345\214\272\345\237\237.md" "b/Sort/1637. \344\270\244\347\202\271\344\271\213\351\227\264\344\270\215\345\214\205\345\220\253\344\273\273\344\275\225\347\202\271\347\232\204\346\234\200\345\256\275\345\236\202\347\233\264\345\214\272\345\237\237.md" new file mode 100644 index 0000000..cf24822 --- /dev/null +++ "b/Sort/1637. \344\270\244\347\202\271\344\271\213\351\227\264\344\270\215\345\214\205\345\220\253\344\273\273\344\275\225\347\202\271\347\232\204\346\234\200\345\256\275\345\236\202\347\233\264\345\214\272\345\237\237.md" @@ -0,0 +1,53 @@ +#### 1637. 两点之间不包含任何点的最宽垂直区域 + +难度:中等 + +--- + +给你 `n` 个二维平面上的点 `points` ,其中 `points[i] = [xi, yi]` ,请你返回两点之间内部不包含任何点的  **最宽垂直区域** 的宽度。 + + **垂直区域** 的定义是固定宽度,而 y 轴上无限延伸的一块区域(也就是高度为无穷大)。 **最宽垂直区域** 为宽度最大的一个垂直区域。 + +请注意,垂直区域  **边上**  的点  **不在**  区域内。 + + **示例 1:** + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/10/31/points3.png)​ +``` +输入:points = [[8,7],[9,9],[7,4],[9,7]] +输出:1 +解释:红色区域和蓝色区域都是最优区域。 +``` + + **示例 2:** + +``` +输入:points = [[3,1],[9,0],[1,0],[1,4],[5,3],[8,8]] +输出:3 +``` + + **提示:** + +* `n == points.length` +* `2 <= n <= 10^5` +* `points[i].length == 2` +* `0 <= xi, yi <= 10^9` + +--- + +自定义排序: + +无需考虑纵坐标,相邻两点横坐标差的最大值即是所要求的值。 + +```Java +class Solution { + public int maxWidthOfVerticalArea(int[][] points) { + Arrays.sort(points, (a, b) -> (a[0] - b[0])); + int ans = 0; + for(int i = 1; i < points.length; i++){ + ans = Math.max(points[i][0] - points[i - 1][0], ans); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Sort/658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" "b/Sort/658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" new file mode 100644 index 0000000..9252a05 --- /dev/null +++ "b/Sort/658. \346\211\276\345\210\260 K \344\270\252\346\234\200\346\216\245\350\277\221\347\232\204\345\205\203\347\264\240.md" @@ -0,0 +1,60 @@ +#### 658. 找到 K 个最接近的元素 + +难度:中等 + +--- + +给定一个 **排序好** 的数组 `arr` ,两个整数 `k` 和 `x` ,从数组中找到最靠近 `x`(两数之差最小)的 `k` 个数。返回的结果必须要是按升序排好的。 + +整数 `a` 比整数 `b` 更接近 `x` 需要满足: + +* `|a - x| < |b - x|` 或者 +* `|a - x| == |b - x|` 且 `a < b` + + **示例 1:** + +``` +输入:arr = [1,2,3,4,5], k = 4, x = 3 +输出:[1,2,3,4] +``` + + **示例 2:** + +``` +输入:arr = [1,2,3,4,5], k = 4, x = -1 +输出:[1,2,3,4] +``` + + **提示:** + +* `1 <= k <= arr.length` +* `1 <= arr.length <= 10^4` +* `arr` 按 **升序** 排列 +* `-10^4 <= arr[i], x <= 10^4` + +--- +自定义排序: + +将数组的每个值与 `x` 的差的绝对值进行重新排序,得到的前 `k` 个结果再重新排序即可得到最后答案。 + + +```Go +func findClosestElements(arr []int, k int, x int) []int { + sort.Slice(arr, func(i, j int) bool { + if abs(arr[i] - x) != abs(arr[j] - x){ + return abs(arr[i] - x) < abs(arr[j] - x) + } + return arr[i] < arr[j] + }) + arr = arr[:k] + sort.Ints(arr) + return arr +} + +func abs(num int) int { + if num < 0 { + return -num + } + return num +} +``` \ No newline at end of file diff --git "a/Sort/912. \345\275\222\345\271\266\346\216\222\345\272\217.md" "b/Sort/912. \345\275\222\345\271\266\346\216\222\345\272\217.md" index 0f16388..bcba7f6 100644 --- "a/Sort/912. \345\275\222\345\271\266\346\216\222\345\272\217.md" +++ "b/Sort/912. \345\275\222\345\271\266\346\216\222\345\272\217.md" @@ -27,7 +27,45 @@ --- -该归并排序有以下两个优化点: +普通的归并排序: + +```java +class Solution { + public int[] sortArray(int[] nums) { + merge(nums, 0, nums.length - 1); + return nums; + } + + private void merge(int[] nums, int left, int right){ + if(left < right){ + int mid = (left + right) / 2; + merge(nums, left, mid); + merge(nums, mid + 1, right); + mergeSort(nums, left, mid, right); + } + } + + private void mergeSort(int[] nums, int left, int mid, int right){ + int i = left, j = mid + 1, index = 0; + int[] temp = new int[right - left + 1]; + while(i <= mid && j <= right){ + if(nums[i] <= nums[j]){ + temp[index++] = nums[i++]; + } + else { + temp[index++] = nums[j++]; + } + } + while(i <= mid) temp[index++] = nums[i++]; + while(j <= right) temp[index++] = nums[j++]; + System.arraycopy(temp, 0, nums, left, right - left + 1); + } +} +``` + + + +优化后的归并排序有以下两个优化点: 1. 区间范围小于 `INSERTION_SORT_THRESHOLD` 的数组使用插入排序 2. 左数组的最后一个数小于或等于右数组的第一个数,证明已经有序 diff --git "a/Sort/912. \345\277\253\351\200\237\346\216\222\345\272\217.md" "b/Sort/912. \345\277\253\351\200\237\346\216\222\345\272\217.md" index 5a173c2..091c23c 100644 --- "a/Sort/912. \345\277\253\351\200\237\346\216\222\345\272\217.md" +++ "b/Sort/912. \345\277\253\351\200\237\346\216\222\345\272\217.md" @@ -40,12 +40,14 @@ class Solution { private void quickSort(int[] nums, int left, int right){ if(right <= left) return; - int pivot = func(nums, left, right); + // int index = new Random().nextInt(right - left + 1) + left; // 随机选取下标并与最左侧元素交换,优化点1 + // swap(nums, left, index); + int pivot = findPivot(nums, left, right); quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } - private int func(int[] nums, int left, int right){ + private int findPivot(int[] nums, int left, int right){ int index = left, temp = nums[left]; while(left < right){ while(left < right && nums[right] >= temp) right--; // 要先从右边开始找! @@ -80,7 +82,7 @@ class Solution { private void quickSort(int[] nums, int left, int right){ if(right <= left) return; - int index = new Random().nextInt(right - left + 1) + left; + int index = new Random().nextInt(right - left + 1) + left; // 随机选取下标并与最左侧元素交换,优化点1 swap(nums, left, index); int lt = left, i = left + 1, gt = right + 1; diff --git "a/Stack/1003. \346\243\200\346\237\245\346\233\277\346\215\242\345\220\216\347\232\204\350\257\215\346\230\257\345\220\246\346\234\211\346\225\210.md" "b/Stack/1003. \346\243\200\346\237\245\346\233\277\346\215\242\345\220\216\347\232\204\350\257\215\346\230\257\345\220\246\346\234\211\346\225\210.md" new file mode 100644 index 0000000..8b07599 --- /dev/null +++ "b/Stack/1003. \346\243\200\346\237\245\346\233\277\346\215\242\345\220\216\347\232\204\350\257\215\346\230\257\345\220\246\346\234\211\346\225\210.md" @@ -0,0 +1,75 @@ +#### 1003. 检查替换后的词是否有效 + +难度:中等 + +--- + +给你一个字符串 `s` ,请你判断它是否 **有效** 。 + +字符串 `s` **有效** 需要满足:假设开始有一个空字符串 `t = ""` ,你可以执行 **任意次** 下述操作将 `t` **转换为** `s` : + +* 将字符串 `"abc"` 插入到 `t` 中的任意位置。形式上,`t` 变为 `tleft + "abc" + tright`,其中 `t == tleft + tright` 。注意,`tleft` 和 `tright` 可能为 **空** 。 + +如果字符串 `s` 有效,则返回 `true`;否则,返回 `false`。 + + **示例 1:** + +``` +输入:s = "aabcbc" +输出:true +解释: +"" -> "abc" -> "aabcbc" +因此,"aabcbc" 有效。 +``` + + **示例 2:** + +``` +输入:s = "abcabcababcc" +输出:true +解释: +"" -> "abc" -> "abcabc" -> "abcabcabc" -> "abcabcababcc" +因此,"abcabcababcc" 有效。 +``` + + **示例 3:** + +``` +输入:s = "abccba" +输出:false +解释:执行操作无法得到 "abccba" 。 +``` + + **提示:** + +* `1 <= s.length <= 2 * 10^4` +* `s` 由字母 `'a'`、`'b'` 和 `'c'` 组成 + +--- + +栈: + +遍历字符串,当**栈的长度大于等于三**时,检查栈的后三位(只能通过连续弹出后三位校验)是否是 `abc`,如果是,则同时弹出这三个字符,否则,重新入栈。 + +```Java +class Solution { + public boolean isValid(String s) { + ArrayDeque stack = new ArrayDeque<>(); + for(int i = 0; i < s.length(); i++){ + char c = s.charAt(i); + stack.push(c); + if(stack.size() >= 3){ + c = stack.pop(); + char temp1 = stack.pop(), temp2 = stack.pop(); + if(c != 'c' || temp1 != 'b' || temp2 != 'a'){ + stack.push(temp2); + stack.push(temp1); + stack.push(c); + } + } + } + if(stack.isEmpty()) return true; + return false; + } +} +``` \ No newline at end of file diff --git "a/Stack/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" "b/Stack/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" new file mode 100644 index 0000000..e2c62a2 --- /dev/null +++ "b/Stack/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.md" @@ -0,0 +1,103 @@ +#### 150. 逆波兰表达式求值 + +难度:中等 + +--- + +给你一个字符串数组 `tokens` ,表示一个根据 [逆波兰表示法](https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437) 表示的算术表达式。 + +请你计算该表达式。返回一个表示表达式值的整数。 + + **注意:** + +* 有效的算符为 `'+'`、`'-'`、`'*'` 和 `'/'` 。 +* 每个操作数(运算对象)都可以是一个整数或者另一个表达式。 +* 两个整数之间的除法总是 **向零截断** 。 +* 表达式中不含除零运算。 +* 输入是一个根据逆波兰表示法表示的算术表达式。 +* 答案及所有中间计算结果可以用 **32 位** 整数表示。 + + **示例 1:** + +``` +输入:tokens = ["2","1","+","3","*"] +输出:9 +解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 +``` + + **示例 2:** + +``` +输入:tokens = ["4","13","5","/","+"] +输出:6 +解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 +``` + + **示例 3:** + +``` +输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] +输出:22 +解释:该算式转化为常见的中缀算术表达式为: + ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 += ((10 * (6 / (12 * -11))) + 17) + 5 += ((10 * (6 / -132)) + 17) + 5 += ((10 * 0) + 17) + 5 += (0 + 17) + 5 += 17 + 5 += 22 +``` + + **提示:** + +* `1 <= tokens.length <= 10^4` +* `tokens[i]` 是一个算符(`"+"`、`"-"`、`"*"` 或 `"/"`),或是在范围 `[-200, 200]` 内的一个整数 + + **逆波兰表达式:** + +逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。 + +* 平常使用的算式则是一种中缀表达式,如 `( 1 + 2 ) * ( 3 + 4 )` 。 +* 该算式的逆波兰表达式写法为 `( ( 1 2 + ) ( 3 4 + ) * )` 。 + +逆波兰表达式主要有以下两个优点: + +* 去掉括号后表达式无歧义,上式即便写成 `1 2 + 3 4 + *` 也可以依据次序计算出正确结果。 +* 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中 + +--- + +栈: + +遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。注意字符相等的判断需要用 `s.equals("+")`,而**不能直接用等于号**。 + +```Java +class Solution { + public int evalRPN(String[] tokens) { + int res = 0; + Deque stack = new ArrayDeque(); + for(String token: tokens){ + if(isNumber(token)){ + stack.push(Integer.parseInt(token)); + } else { + int num1 = stack.pop(); + int num2 = stack.pop(); + if(token.equals("+")){ + stack.push(num2 + num1); + } else if(token.equals("-")){ + stack.push(num2 - num1); + } else if(token.equals("*")){ + stack.push(num2 * num1); + } else if(token.equals("/")){ + stack.push(num2 / num1); + } + } + } + return stack.pop(); + } + + private boolean isNumber(String s){ + return !(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")); + } +} +``` \ No newline at end of file diff --git "a/Stack/155. \346\234\200\345\260\217\346\240\210.md" "b/Stack/155. \346\234\200\345\260\217\346\240\210.md" new file mode 100644 index 0000000..7b3ae7e --- /dev/null +++ "b/Stack/155. \346\234\200\345\260\217\346\240\210.md" @@ -0,0 +1,88 @@ +#### 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. +``` + + **提示:** + +* `-2^31 <= val <= 2^31 - 1` +* `pop`、`top` 和 `getMin` 操作总是在 **非空栈** 上调用 +* `push`, `pop`, `top`, and `getMin`最多被调用 `3 * 10^4` 次 + +--- + +栈: + +使用辅助栈,其元素的值为原栈中的最小值。在初始化阶段向辅助栈中注入最大值元素,以便空栈情况下的判断。 + +```Java +class MinStack { + + ArrayDeque stack, minStack; + + public MinStack() { + stack = new ArrayDeque<>(); + minStack = new ArrayDeque<>(); + minStack.push(Integer.MAX_VALUE); + } + + public void push(int val) { + stack.push(val); + minStack.push(Math.min(minStack.peek(), val)); + } + + public void pop() { + stack.pop(); + minStack.pop(); + } + + public int top() { + return stack.peek(); + } + + public int getMin() { + return minStack.peek(); + } +} + +/** + * 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(); + */ +``` \ No newline at end of file diff --git "a/Stack/2390. \344\273\216\345\255\227\347\254\246\344\270\262\344\270\255\347\247\273\351\231\244\346\230\237\345\217\267.md" "b/Stack/2390. \344\273\216\345\255\227\347\254\246\344\270\262\344\270\255\347\247\273\351\231\244\346\230\237\345\217\267.md" new file mode 100644 index 0000000..b77d47a --- /dev/null +++ "b/Stack/2390. \344\273\216\345\255\227\347\254\246\344\270\262\344\270\255\347\247\273\351\231\244\346\230\237\345\217\267.md" @@ -0,0 +1,69 @@ +#### 2390. 从字符串中移除星号 + +难度:中等 + +--- + +给你一个包含若干星号 `*` 的字符串 `s` 。 + +在一步操作中,你可以: + +* 选中 `s` 中的一个星号。 +* 移除星号 **左侧** 最近的那个 **非星号** 字符,并移除该星号自身。 + +返回移除 **所有** 星号之后的字符串 **。** + + **注意:** + +* 生成的输入保证总是可以执行题面中描述的操作。 +* 可以证明结果字符串是唯一的。 + + **示例 1:** + +``` +输入:s = "leet**cod*e" +输出:"lecoe" +解释:从左到右执行移除操作: +- 距离第 1 个星号最近的字符是 "leet**cod*e" 中的 't' ,s 变为 "lee*cod*e" 。 +- 距离第 2 个星号最近的字符是 "lee*cod*e" 中的 'e' ,s 变为 "lecod*e" 。 +- 距离第 3 个星号最近的字符是 "lecod*e" 中的 'd' ,s 变为 "lecoe" 。 +不存在其他星号,返回 "lecoe" 。 +``` + + **示例 2:** + +``` +输入:s = "erase*****" +输出:"" +解释:整个字符串都会被移除,所以返回空字符串。 +``` + + **提示:** + +* `1 <= s.length <= 10^5` +* `s` 由小写英文字母和星号 `*` 组成 +* `s` 可以执行上述操作 + +--- + +栈: + +简单**栈模拟**,没有其他需要注意的点。 + +```Java +class Solution { + public String removeStars(String s) { + ArrayDeque stack = new ArrayDeque<>(); + for(int i = 0; i < s.length(); i++){ + char c = s.charAt(i); + if(c == '*' && !stack.isEmpty()) stack.pop(); + else if(c != '*') stack.push(c); + } + StringBuilder sb = new StringBuilder(); + while(!stack.isEmpty()){ + sb.append("" + stack.pollLast()); + } + return sb.toString(); + } +} +``` \ No newline at end of file diff --git "a/Stack/394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" "b/Stack/394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" new file mode 100644 index 0000000..c8a8ec6 --- /dev/null +++ "b/Stack/394. \345\255\227\347\254\246\344\270\262\350\247\243\347\240\201.md" @@ -0,0 +1,94 @@ +#### 394. 字符串解码 + +难度:中等 + +--- + +给定一个经过编码的字符串,返回它解码后的字符串。 + +编码规则为: `k[encoded_string]`,表示其中方括号内部的 `encoded_string` 正好重复 `k` 次。注意 `k` 保证为正整数。 + +你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 + +此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 `k` ,例如不会出现像 `3a` 或 `2[4]` 的输入。 + + **示例 1:** + +``` +输入:s = "3[a]2[bc]" +输出:"aaabcbc" +``` + + **示例 2:** + +``` +输入:s = "3[a2[c]]" +输出:"accaccacc" +``` + + **示例 3:** + +``` +输入:s = "2[abc]3[cd]ef" +输出:"abcabccdcdcdef" +``` + + **示例 4:** + +``` +输入:s = "abc3[cd]xyz" +输出:"abccdcdcdxyz" +``` + + **提示:** + +* `1 <= s.length <= 30` +* `s` 由小写英文字母、数字和方括号 `'[]'` 组成 +* `s` 保证是一个  **有效**  的输入。 +* `s` 中所有整数的取值范围为 `[1, 300]` + +--- + +栈: + +**注意 `k` 可能是两位以上的数。** + +```Java +class Solution { + public String decodeString(String s) { + int n = s.length(); + ArrayDeque number = new ArrayDeque<>(); + ArrayDeque stack = new ArrayDeque<>(); + for(int i = 0; i < n; i++){ + if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){ + int num = 0; + while(s.charAt(i) >= '0' && s.charAt(i) <= '9'){ + num = num * 10 + s.charAt(i) - '0'; + i++; + } + i--; + number.push(num); + } + else if(s.charAt(i) != ']') stack.push(s.charAt(i)); + else { + StringBuilder sb = new StringBuilder(); + while(!stack.isEmpty() && stack.peek() != '['){ + sb.append(stack.pop()); + } + if(!stack.isEmpty()) stack.pop(); + int times = number.pop(); + for(int j = 0; j < times; j++){ + for(int k = sb.length() - 1; k >= 0; k--){ + stack.push(sb.charAt(k)); + } + } + } + } + StringBuilder ans = new StringBuilder(); + while(!stack.isEmpty()){ + ans.append(stack.pop()); + } + return ans.reverse().toString(); + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" "b/Two Pointers/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" new file mode 100644 index 0000000..945c891 --- /dev/null +++ "b/Two Pointers/1023. \351\251\274\345\263\260\345\274\217\345\214\271\351\205\215.md" @@ -0,0 +1,108 @@ +#### 1023. 驼峰式匹配 + +难度:中等 + +--- + +如果我们可以将 **小写字母** 插入模式串 `pattern` 得到待查询项 `query`,那么待查询项与给定模式串匹配。(我们可以在任何位置插入每个字符,也可以插入 0 个字符。) + +给定待查询列表 `queries`,和模式串 `pattern`,返回由布尔值组成的答案列表 `answer`。只有在待查项 `queries[i]` 与模式串 `pattern` 匹配时, `answer[i]` 才为 `true`,否则为 `false`。 + + **示例 1:** + +``` +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FB" +输出:[true,false,true,true,false] +示例: +"FooBar" 可以这样生成:"F" + "oo" + "B" + "ar"。 +"FootBall" 可以这样生成:"F" + "oot" + "B" + "all". +"FrameBuffer" 可以这样生成:"F" + "rame" + "B" + "uffer". +``` + + **示例 2:** + +``` +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBa" +输出:[true,false,true,false,false] +解释: +"FooBar" 可以这样生成:"Fo" + "o" + "Ba" + "r". +"FootBall" 可以这样生成:"Fo" + "ot" + "Ba" + "ll". +``` + + **示例 3:** + +``` +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBaT" +输出:[false,true,false,false,false] +解释: +"FooBarTest" 可以这样生成:"Fo" + "o" + "Ba" + "r" + "T" + "est". +``` + + **提示:** + +1. `1 <= queries.length <= 100` +2. `1 <= queries[i].length <= 100` +3. `1 <= pattern.length <= 100` +4. 所有字符串都仅由大写和小写英文字母组成。 + +--- + +双指针: + +第一个指针 `c1 = queries[i].charAt(first)`,另一个指针 `c2 = pattern.charAt(second)`, 分四种情况: + +1. `c1` 和 `c2` 均为大写; +2. `c1` 和 `c2` 均为小写; +3. `c1` 小写,`c2` 大写; +4. `c1` 大写,`c2` 小写; + +遍历结束后,如果第一个指针没有超出范围,则继续遍历第一个字符串,如果后续存在大写字母,则此次查询结果为 `false`。 + +如果第二个指针没有超出范围,意味着第一个字符串里有第二个字符串没有的字符,因此此次查询结果为 `false`。 + +```Java +class Solution { + public List camelMatch(String[] queries, String pattern) { + List ans = new ArrayList<>(); + for(int i = 0; i < queries.length; i++){ + boolean flag = true; + int first = 0, second = 0; + while(first < queries[i].length() && second < pattern.length()){ + char c1 = queries[i].charAt(first), c2 = pattern.charAt(second); + if(Character.isUpperCase(c1) && Character.isUpperCase(c2)){ + if(c1 == c2){ + first++; + second++; + } else { + flag = false; + break; + } + } + else if(Character.isLowerCase(c1) && Character.isLowerCase(c2)){ + first++; + if(c1 == c2) second++; + } + else if(Character.isLowerCase(c1) && Character.isUpperCase(c2)){ + first++; + } else { + flag = false; + break; + } + } + while(first < queries[i].length()){ + char c1 = queries[i].charAt(first); + if(Character.isUpperCase(c1)){ + flag = false; + break; + } + first++; + } + if(second < pattern.length()){ + flag = false; + } + ans.add(flag); + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" "b/Two Pointers/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" new file mode 100644 index 0000000..d32a893 --- /dev/null +++ "b/Two Pointers/11. \347\233\233\346\234\200\345\244\232\346\260\264\347\232\204\345\256\271\345\231\250.md" @@ -0,0 +1,61 @@ +#### 11. 盛最多水的容器 + +难度:中等 + +--- + +给定一个长度为 `n` 的整数数组 `height` 。有 `n` 条垂线,第 `i` 条线的两个端点是 `(i, 0)` 和 `(i, height[i])` 。 + +找出其中的两条线,使得它们与 `x` 轴共同构成的容器可以容纳最多的水。 + +返回容器可以储存的最大水量。 + + **说明:** 你不能倾斜容器。 + + **示例 1:** + +![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) + +``` +输入:[1,8,6,2,5,4,8,3,7] +输出:49 +解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +``` + + **示例 2:** + +``` +输入:height = [1,1] +输出:1 +``` + + **提示:** + +* `n == height.length` +* `2 <= n <= 10^5` +* `0 <= height[i] <= 10^4` + +--- + +双指针: + +1. 左指针指向左边界,右指针指向右边界 +2. 不断的收紧左右边界,选定**两边界中相对短的边界并向内移动一格** +3. 返回最大面积 + +```Java +class Solution { + public int maxArea(int[] height) { + int left = 0, right = height.length - 1, res = 0; + while(left < right){ + res = Math.max(res, Math.min(height[left], height[right]) * (right - left)); + if(height[left] < height[right]){ + left++; + } else { + right--; + } + } + return res; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/15. \344\270\211\346\225\260\344\271\213\345\222\214.md" "b/Two Pointers/15. \344\270\211\346\225\260\344\271\213\345\222\214.md" index 3d90f15..ace9e3e 100644 --- "a/Two Pointers/15. \344\270\211\346\225\260\344\271\213\345\222\214.md" +++ "b/Two Pointers/15. \344\270\211\346\225\260\344\271\213\345\222\214.md" @@ -72,8 +72,14 @@ class Solution { left++; right--; } - else if(sum < 0) left++; - else right--; + else if(sum < 0){ + while(left < right && nums[left] == nums[left + 1]) left++; + left++; + } + else { + while(left < right && nums[right] == nums[right - 1]) right--; + right--; + } } } return ans; diff --git "a/Two Pointers/151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" "b/Two Pointers/151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" new file mode 100644 index 0000000..d0cdd5b --- /dev/null +++ "b/Two Pointers/151. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.md" @@ -0,0 +1,70 @@ +#### 151. 反转字符串中的单词 + +难度:中等 + +--- + +给你一个字符串 `s` ,请你反转字符串中 **单词** 的顺序。 + + **单词** 是由非空格字符组成的字符串。`s` 中使用至少一个空格将字符串中的 **单词** 分隔开。 + +返回 **单词** 顺序颠倒且 **单词** 之间用单个空格连接的结果字符串。 + + **注意:** 输入字符串 `s`中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 + + **示例 1:** + +``` +输入:s = "the sky is blue" +输出:"blue is sky the" +``` + + **示例 2:** + +``` +输入:s = "  hello world  " +输出:"world hello" +解释:反转后的字符串中不能存在前导空格和尾随空格。 +``` + + **示例 3:** + +``` +输入:s = "a good   example" +输出:"example good a" +解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。 +``` + + **提示:** + +* `1 <= s.length <= 10^4` +* `s` 包含英文大小写字母、数字和空格 `' '` +* `s` 中 **至少存在一个** 单词 + + **进阶:** 如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 `O(1)` 额外空间复杂度的 **原地** 解法。 + +--- + +双指针: + +第一个指针指向单词**首字母的前一位**,第二个指向**单词尾字母**。两个指针分别是在空格和非空格时移动。 + +```Java +class Solution { + public String reverseWords(String s) { + int i = s.length() - 1, j = i; + StringBuilder sb = new StringBuilder(); + while(i >= 0){ + while(i >= 0 && s.charAt(i) == ' '){ + i--; + j = i; + } + while(i >= 0 && s.charAt(i) != ' '){ + i--; + } + sb.append(s.substring(i + 1, j + 1) + " "); + } + return sb.toString().trim(); + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/1616. \345\210\206\345\211\262\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\345\276\227\345\210\260\345\233\236\346\226\207\344\270\262.md" "b/Two Pointers/1616. \345\210\206\345\211\262\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\345\276\227\345\210\260\345\233\236\346\226\207\344\270\262.md" new file mode 100644 index 0000000..9b3305c --- /dev/null +++ "b/Two Pointers/1616. \345\210\206\345\211\262\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\345\276\227\345\210\260\345\233\236\346\226\207\344\270\262.md" @@ -0,0 +1,82 @@ +#### 1616. 分割两个字符串得到回文串 + +难度:中等 + +--- + +给你两个字符串 `a` 和 `b` ,它们长度相同。请你选择一个下标,将两个字符串都在  **相同的下标** 分割开。由 `a` 可以得到两个字符串: `aprefix` 和 `asuffix` ,满足 `a = aprefix + asuffix` ,同理,由 `b` 可以得到两个字符串 `bprefix` 和 `bsuffix` ,满足 `b = bprefix + bsuffix` 。请你判断 `aprefix + bsuffix` 或者 `bprefix + asuffix` 能否构成回文串。 + +当你将一个字符串 `s` 分割成 `sprefix` 和 `ssuffix` 时, `ssuffix` 或者 `sprefix` 可以为空。比方说, `s = "abc"` 那么 `"" + "abc"` , `"a" + "bc"` , `"ab" + "c"` 和 `"abc" + ""` 都是合法分割。 + +如果 **能构成回文字符串** ,那么请返回 `true`,否则返回 `false` 。 + + **注意** , `x + y` 表示连接字符串 `x` 和 `y` 。 + + **示例 1:** + +``` +输入:a = "x", b = "y" +输出:true +解释:如果 a 或者 b 是回文串,那么答案一定为 true ,因为你可以如下分割: +aprefix = "", asuffix = "x" +bprefix = "", bsuffix = "y" +那么 aprefix + bsuffix = "" + "y" = "y" 是回文串。 +``` + + **示例 2:** + +``` +输入:a = "abdef", b = "fecab" +输出:true +``` + + **示例 3:** + +``` +输入:a = "ulacfd", b = "jizalu" +输出:true +解释:在下标为 3 处分割: +aprefix = "ula", asuffix = "cfd" +bprefix = "jiz", bsuffix = "alu" +那么 aprefix + bsuffix = "ula" + "alu" = "ulaalu" 是回文串。 +``` + + **提示:** + +* `1 <= a.length, b.length <= 10^5` +* `a.length == b.length` +* `a` 和 `b` 都只包含小写英文字母 + +--- + +双指针: + +1. 用两个指针分别表示字符串A的前缀与字符串B的后缀在哪个下标位置不构成回文字符串(另一种情况是A的后缀和B的前缀,因此用逻辑运算符**或**连接这两种情况的返回值)。对于下列字符串,当A的下标为4和B的下标为17时(字符串A下标在0-3与字符串B下标在18-21时可组成回文字符串)。 +2. 接着判断字符串A和B在这两个下标之间是否构成回文字符串,任意一个字符串满足条件则返回true,否则返回false。对于下列字符串,字符串B在下标4-17之间是回文,因此返回true。 + + + +![](https://raw.githubusercontent.com/CompetitiveLin/ImageHostingService/picgo/imgs/202303181157470.png) + +```Java +class Solution { + public boolean checkPalindromeFormation(String a, String b) { + return checkConcatenation(a, b) || checkConcatenation(b, a); + } + private boolean checkConcatenation(String a, String b){ + int n = a.length(), left = 0, right = n - 1; + while(left < right && a.charAt(left) == b.charAt(right)){ + left++; + right--; + } + if(left >= right) return true; + return checkSelfPalindrome(a, left, right) || checkSelfPalindrome(b, left, right); + } + private boolean checkSelfPalindrome(String s, int left, int right){ + while(left < right){ + if(s.charAt(left++) != s.charAt(right--)) return false; + } + return true; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/1679. K \345\222\214\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" "b/Two Pointers/1679. K \345\222\214\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" new file mode 100644 index 0000000..7dbb3c4 --- /dev/null +++ "b/Two Pointers/1679. K \345\222\214\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256.md" @@ -0,0 +1,65 @@ +#### 1679. K 和数对的最大数目 + +难度:中等 + +--- + +给你一个整数数组 `nums` 和一个整数 `k` 。 + +每一步操作中,你需要从数组中选出和为 `k` 的两个整数,并将它们移出数组。 + +返回你可以对数组执行的最大操作数。 + + **示例 1:** + +``` +输入:nums = [1,2,3,4], k = 5 +输出:2 +解释:开始时 nums = [1,2,3,4]: +- 移出 1 和 4 ,之后 nums = [2,3] +- 移出 2 和 3 ,之后 nums = [] +不再有和为 5 的数对,因此最多执行 2 次操作。 +``` + + **示例 2:** + +``` +输入:nums = [3,1,3,4,3], k = 6 +输出:1 +解释:开始时 nums = [3,1,3,4,3]: +- 移出前两个 3 ,之后nums = [1,4,3] +不再有和为 6 的数对,因此最多执行 1 次操作。 +``` + + **提示:** + +* `1 <= nums.length <= 10^5` +* `1 <= nums[i] <= 10^9` +* `1 <= k <= 10^9` + +--- + +排序 + 双指针: + +先将数组升序排序,当双指针元素之和小于 `k` 时,左指针右移一位;大于 `k` 时,右指针左移一位;等于 `k` 时,同时移动双指针。 + +```Java +class Solution { + public int maxOperations(int[] nums, int k) { + Arrays.sort(nums); + int left = 0, right = nums.length - 1, ans = 0; + while(left < right){ + if(nums[left] + nums[right] < k){ + left++; + } else if(nums[left] + nums[right] > k){ + right--; + } else { + ans++; + left++; + right--; + } + } + return ans; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" "b/Two Pointers/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" new file mode 100644 index 0000000..fc8deff --- /dev/null +++ "b/Two Pointers/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.md" @@ -0,0 +1,76 @@ +#### 26. 删除有序数组中的重复项 + +难度:简单 + +--- + +给你一个 **非严格递增排列** 的数组 `nums` ,请你 **[原地](http://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95)** 删除重复出现的元素,使每个元素 **只出现一次** ,返回删除后数组的新长度。元素的 **相对顺序** 应该保持 **一致** 。然后返回 `nums` 中唯一元素的个数。 + +考虑 `nums` 的唯一元素的数量为 `k` ,你需要做以下事情确保你的题解可以被通过: + +* 更改数组 `nums` ,使 `nums` 的前 `k` 个元素包含唯一元素,并按照它们最初在 `nums` 中出现的顺序排列。`nums` 的其余元素与 `nums` 的大小不重要。 +* 返回 `k` 。 + + **判题标准:** + +系统会用下面的代码来测试你的题解: + +``` +int[] nums = [...]; // 输入数组 +int[] expectedNums = [...]; // 长度正确的期望答案 + +int k = removeDuplicates(nums); // 调用 + +assert k == expectedNums.length; +for (int i = 0; i < k; i++) { + assert nums[i] == expectedNums[i]; +} +``` + +如果所有断言都通过,那么您的题解将被 **通过** 。 + + **示例 1:** + +``` +输入:nums = [1,1,2] +输出:2, nums = [1,2,_] +解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 +``` + + **示例 2:** + +``` +输入:nums = [0,0,1,1,1,2,2,3,3,4] +输出:5, nums = [0,1,2,3,4] +解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 +``` + + **提示:** + +* `1 <= nums.length <= 3 * 10^4` +* `-10^4 <= nums[i] <= 10^4` +* `nums` 已按 **非严格递增**  排列 + +--- + +双指针: + +可以理解为快慢指针,`left` 指针指向将被替代的值,`right` 快指针指向待检查的值。 + +判断两指针的元素是否相等,若不相等则将 `right` 指针的元素交给 `left + 1` 并**右移左右指针**;否则只移动右指针。 + +```Java +class Solution { + public int removeDuplicates(int[] nums) { + int n = nums.length, left = 0, right = 0; + if(n <= 1) return n; + while(right < n){ + if(nums[right] != nums[left]){ + nums[++left] = nums[right]; + } + right++; + } + return left + 1; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/3101. \344\272\244\346\233\277\345\255\220\346\225\260\347\273\204\350\256\241\346\225\260.md" "b/Two Pointers/3101. \344\272\244\346\233\277\345\255\220\346\225\260\347\273\204\350\256\241\346\225\260.md" new file mode 100644 index 0000000..e5f6a00 --- /dev/null +++ "b/Two Pointers/3101. \344\272\244\346\233\277\345\255\220\346\225\260\347\273\204\350\256\241\346\225\260.md" @@ -0,0 +1,62 @@ +#### 3101. 交替子数组计数 + +难度:中等 + +--- + +给你一个 二进制数组 `nums` 。 + +如果一个 子数组 中 **不存在** 两个 **相邻** 元素的值 **相同** 的情况,我们称这样的子数组为 **交替子数组** 。 + +返回数组 `nums` 中交替子数组的数量。 + + **示例 1:** + + **输入:** nums = \[0,1,1,1\] + + **输出:** 5 + + **解释:** + +以下子数组是交替子数组:`[0]` 、`[1]` 、`[1]` 、`[1]` 以及 `[0,1]` 。 + + **示例 2:** + + **输入:** nums = \[1,0,1,0\] + + **输出:** 10 + + **解释:** + +数组的每个子数组都是交替子数组。可以统计在内的子数组共有 10 个。 + + **提示:** + +* `1 <= nums.length <= 10^5` +* `nums[i]` 不是 `0` 就是 `1` 。 + +--- + +双指针:找到数组里**每个最长交替数组**,每个最长数组的交替子数组的和为 $len * (len + 1) / 2$,将每个交替子数组的和求和即可。 + +```Go +func countAlternatingSubarrays(nums []int) int64 { + n := len(nums) + res := int64(0) + left, right := 0, 0 + for right < n { + if right + 1 < n && nums[right] != nums[right + 1] { + right++ + } else { + res += calculate(right - left + 1) + right = right + 1 + left = right + } + } + return res +} + +func calculate(length int) int64 { + return int64(length) * int64(length + 1) / 2 +} +``` \ No newline at end of file diff --git "a/Two Pointers/75. \351\242\234\350\211\262\345\210\206\347\261\273.md" "b/Two Pointers/75. \351\242\234\350\211\262\345\210\206\347\261\273.md" new file mode 100644 index 0000000..88362f9 --- /dev/null +++ "b/Two Pointers/75. \351\242\234\350\211\262\345\210\206\347\261\273.md" @@ -0,0 +1,68 @@ +#### 75. 颜色分类 + +难度:中等 + +--- + +给定一个包含红色、白色和蓝色、共 `n` 个元素的数组 `nums` , **[原地](https://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95)** 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 + +我们使用整数 `0`、 `1` 和 `2` 分别表示红色、白色和蓝色。 + +必须在不使用库内置的 sort 函数的情况下解决这个问题。 + + **示例 1:** + +``` +输入:nums = [2,0,2,1,1,0] +输出:[0,0,1,1,2,2] +``` + + **示例 2:** + +``` +输入:nums = [2,0,1] +输出:[0,1,2] +``` + + **提示:** + +* `n == nums.length` +* `1 <= n <= 300` +* `nums[i]` 为 `0`、`1` 或 `2` + + **进阶:** + +* 你能想出一个仅使用常数空间的一趟扫描算法吗? + +--- + +双指针: + +**尾指针**指向最后,**头指针**指向最前。另外需要一个**遍历指针**从头向尾遍历,然后分类讨论(重要): + +1. 如果当前元素为 `0`,与头指针元素交换并将遍历指针和头指针都向右移动一个单位。 +2. 如果当前元素为 `1`,只移动遍历指针 +3. 如果当前元素为 `2`,与尾指针元素交换并左移尾指针,此时不移动遍历指针!!! + +```Java +class Solution { + public void sortColors(int[] nums) { + int left = 0, right = nums.length - 1, index = 0; + while(index <= right){ + if(nums[index] == 2){ + swap(nums, index, right--); + continue; + } else if(nums[index] == 0){ + swap(nums, left++, index); + } + index++; + } + } + + private void swap(int[] nums, int left, int right){ + int temp = nums[left]; + nums[left] = nums[right]; + nums[right] = temp; + } +} +``` \ No newline at end of file diff --git "a/Two Pointers/80. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" "b/Two Pointers/80. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" new file mode 100644 index 0000000..e67dd51 --- /dev/null +++ "b/Two Pointers/80. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271 II.md" @@ -0,0 +1,95 @@ +#### 80. 删除有序数组中的重复项 II + +难度:中等 + +--- + +给你一个有序数组 `nums` ,请你 **[原地](http://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95)** 删除重复出现的元素,使得出现次数超过两次的元素 **只出现两次** ,返回删除后数组的新长度。 + +不要使用额外的数组空间,你必须在 **[原地](https://baike.baidu.com/item/%E5%8E%9F%E5%9C%B0%E7%AE%97%E6%B3%95) 修改输入数组** 并在使用 O(1) 额外空间的条件下完成。 + + **说明:** + +为什么返回数值是整数,但输出的答案是数组呢? + +请注意,输入数组是以 **「引用」** 方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 + +你可以想象内部操作如下: + +``` +// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 +int len = removeDuplicates(nums); + +// 在函数里修改输入数组对于调用者是可见的。 +// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 +for (int i = 0; i < len; i++) { +    print(nums[i]); +} +``` + + **示例 1:** + +``` +输入:nums = [1,1,1,2,2,3] +输出:5, nums = [1,1,2,2,3] +解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。 +``` + + **示例 2:** + +``` +输入:nums = [0,0,1,1,1,1,2,3,3] +输出:7, nums = [0,0,1,1,2,3,3] +解释:函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。 +``` + + **提示:** + +* `1 <= nums.length <= 3 * 10^4` +* `-10^4 <= nums[i] <= 10^4` +* `nums` 已按升序排列 + +--- + +快慢指针: + +慢指针记录被替换的下标,快指针记录待检查的下标。 + +```Java +//Java +class Solution { + public int removeDuplicates(int[] nums) { + int n = nums.length; + if(n <= 2) return n; + int slow = 2, fast = 2; + while(fast < n){ + if(nums[slow - 2] != nums[fast]){ + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + return slow; + } +} +``` + + +```go +//Go +func removeDuplicates(nums []int) int { + n := len(nums) + if n <= 2 { + return n + } + slow, fast := 2, 2 + for ;fast < n; { + if nums[slow - 2] != nums[fast] { + nums[slow] = nums[fast] + slow++ + } + fast++ + } + return slow +} +``` \ No newline at end of file diff --git a/goroutine.md b/goroutine.md new file mode 100644 index 0000000..37e47c2 --- /dev/null +++ b/goroutine.md @@ -0,0 +1,187 @@ +# Goroutine + +1. 两个协程轮流打印 `1, 2` + +```go +package main + +import ( + "fmt" + "sync" +) + +var wg sync.WaitGroup + +func main() { + wg.Add(2) + ch1 := make(chan int) + go func() { + defer wg.Done() + for i := 0; i < 10; i++ { + ch1 <- i + if i%2 == 0 { + fmt.Println("1: ", i) + } + } + }() + go func() { + defer wg.Done() + for i := 0; i < 10; i++ { + <-ch1 + if i%2 == 1 { + fmt.Println("2: ", i) + } + } + }() + wg.Wait() +} +``` + +2. 三个协程轮流打印 `1, 2, 3` + +```go +package main + +import ( + "fmt" + "sync" +) + +func main() { + var wg sync.WaitGroup + wg.Add(2) + ch1 := make(chan int) + ch2 := make(chan int) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + <-ch1 + <-ch2 + if i%3 == 0 { + fmt.Println(1, ": ", i) + } + } + }() + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + ch1 <- 1 + if i%3 == 1 { + fmt.Println(2, ": ", i) + } + } + }() + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + ch2 <- 1 + if i%3 == 2 { + fmt.Println(3, ": ", i) + } + } + }() + wg.Wait() +} +``` + +3. 生产者消费者模型 + +```go +package main + +import ( + "fmt" + "sync" +) + +var wg sync.WaitGroup +var lock sync.Mutex + +func main() { + ch := make(chan int, 100) + producer(ch) + cnt := 3 + wg.Add(cnt) + for i := 0; i < cnt; i++ { + go consumer(i, ch) + } + wg.Wait() +} + +func producer(ch chan int) { + defer close(ch) + for i := 0; i < 10; i++ { + fmt.Println("producer: ", i) + ch <- i + } +} + +func consumer(i int, ch chan int) { + for { + if v, ok := <-ch; ok { + fmt.Println("consumer ", i, ":", v) + } else { + break + } + } + wg.Done() +} +``` + + + +## 手写协程池 + +```go +package main + +import ( + "fmt" + "time" +) + +type Task struct { + f func() +} + +func (t *Task) Execute() { + t.f() +} + +type Pool struct { + tasks chan *Task + entry chan *Task + nums int +} + +func (p *Pool) Work(id int) { + for task := range p.tasks { + task.Execute() + fmt.Println("excute id: ", id) + time.Sleep(2 * time.Second) + } +} + +func (p *Pool) Run() { + for i := 1; i <= p.nums; i++ { + go p.Work(i) + } + for task := range p.entry { + p.tasks <- task + } +} + +func main() { + task := &Task{f: func() { + fmt.Println("Execute!", time.Now()) + }} + p := &Pool{nums: 3, entry: make(chan *Task), tasks: make(chan *Task)} + go func() { + for { + p.entry <- task + } + }() + p.Run() +} +``` + diff --git a/leetcode.py b/leetcode.py index 4ba1aac..cf028e6 100644 --- a/leetcode.py +++ b/leetcode.py @@ -4,9 +4,9 @@ """ @project: PyCharm @file: leetcode.py -@author: Shengqiang Zhang -@time: 2020/8/13 22:24 -@mail: sqzhang77@gmail.com +@author: CompetitiveLin +@time: 2023/09/01 10:58 +@mail: zee_lin@foxmail.com """ import sys @@ -19,29 +19,13 @@ # 登录 -def login(EMAIL, PASSWORD): +def login(LEETCODE_SESSION): session = requests.Session() # 建立会话 session.mount('http://', HTTPAdapter(max_retries=6)) # 超时重试次数 session.mount('https://', HTTPAdapter(max_retries=6)) # 超时重试次数 session.encoding = "utf-8" # 编码格式 - - # 使用账号密码方式登录, 请确保账号密码正确 - login_data = { - 'login': EMAIL, - 'password': PASSWORD - } - - sign_in_url = 'https://leetcode.cn/accounts/login/' - headers = {'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36", 'Connection': 'keep-alive', 'Referer': sign_in_url, "origin": "https://leetcode.cn/"} - - # 发送登录请求 - session.post(sign_in_url, headers=headers, data=login_data, timeout=10, allow_redirects=False) - is_login = session.cookies.get('LEETCODE_SESSION') != None - if is_login: - print("登录成功!") - return session - else: - raise Exception("登录失败, 请检查账号密码是否正确!") + session.cookies.set('LEETCODE_SESSION', LEETCODE_SESSION) + return session # 获取某个题目的提交记录 @@ -100,27 +84,25 @@ def get_accepted_problems(session): r = session.post(url, data=payload, headers=headers, verify=False) response_data = json.loads(r.text) - return response_data + return response_data['data']['userProfileQuestions'] # 生成Markdown文本 def generate_markdown_text(response_data, session): - + response_data = response_data['questions'] + # 相关介绍 - response_data = response_data['data']['userProfileQuestions']['questions'] markdown_text = "## 相关介绍\n\n" markdown_text += "这是一个 [LeetCode](https://leetcode.cn/problemset/all/) 题目自动统计及分析程序,可自动统计所有提交通过的题目,并以 Markdown 的形式展示。\n\n" markdown_text += "根据个人需求,目前只考虑获取**提交次数**和**重刷次数**这两个指标,以便更好地进行刷题。\n\n" markdown_text += "采用 GitHub Actions 进行自动化部署,无需本地服务器资源。\n\n" markdown_text += "## 使用教程\n\n" - markdown_text += "1. 由模板项目[生成](https://github.com/shengqiangzhang/leetcode-revise/generate)自己的项目\n\n" + markdown_text += "1. 由模板项目[生成](https://github.com/CompetitiveLin/leetcode-revise/generate)自己的项目\n\n" markdown_text += "2. 点击生成项目下的 Settings -> Secrets -> Actions -> New repository secret,分别添加以下 Secrets:\n" - markdown_text += " - Name:`LEETCODE_EMAIL`\n" - markdown_text += " - Secret:你的LeetCode账号\n" - markdown_text += " - Name:`LEETCODE_PASSWORD`\n" - markdown_text += " - Secret:你的LeetCode密码\n\n" + markdown_text += " - Name:`LEETCODE_SESSION`\n" + markdown_text += " - Secret:已登录 LeetCode 账号的浏览器中 Cookie 名为 LEETCODE_SESSION 的值\n\n" markdown_text += "3. 回到项目首页并进入 Actions 部分,在左侧点击`Github LeetCode Bot`,再点击蓝色提示框中的 `Run workflow`,最后点击绿色的 `Run workflow` 按钮即可\n\n" markdown_text += "## 补充说明\n\n" markdown_text += "默认配置为每12小时更新一次,可根据需求修改 [action.yml](.github/workflows/action.yml#L9) 文件的第 `9` 行。如有其他需求,欢迎提交PR。\n\n" @@ -178,10 +160,12 @@ def generate_markdown_text(response_data, session): if __name__ == '__main__': - session = login(sys.argv[1], sys.argv[2]) # 登录,第一个参数为leetcode邮箱账号,第二个参数为leetcode密码 + session = login(sys.argv[1]) # 登录,参数为已登录 LeetCode 账号的浏览器中 Cookie 名为 LEETCODE_SESSION 的值 response_data = get_accepted_problems(session) # 获取所有通过的题目列表 - markdown_text = generate_markdown_text(response_data, session) # 生成Markdown文本 - - # 更新README.md文件 - with open("README.md", "w") as f: - f.write(markdown_text) + if response_data: + markdown_text = generate_markdown_text(response_data, session) # 生成Markdown文本 + # 更新README.md文件 + with open("README.md", "w") as f: + f.write(markdown_text) + else: + raise Exception("Cookies 已过期,请更新 LEETCODE_SESSION") diff --git "a/\344\270\255\346\226\207\346\225\260\345\255\227\350\275\254\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227.md" "b/\344\270\255\346\226\207\346\225\260\345\255\227\350\275\254\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227.md" new file mode 100644 index 0000000..c20a98d --- /dev/null +++ "b/\344\270\255\346\226\207\346\225\260\345\255\227\350\275\254\351\230\277\346\213\211\344\274\257\346\225\260\345\255\227.md" @@ -0,0 +1,45 @@ +# 中文数字转阿拉伯数字 + + + +字符串处理问题,需要考虑 `万亿`, `十万` 等情况。 + +```go +package main + +import "fmt" + +func main() { + fmt.Println(solution("二百")) +} + +func solution(chineseNum string) int { + res, part, temp := 0, 0, 0 + numberDict := map[string]int{"零": 0, "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9} + unitDict := map[string]int{"十": 10, "百": 100, "千": 1000} + largeUnitDict := map[string]int{"万": 10000} // “十万” + largerUnitDict := map[string]int{"亿": 100000000} // “万亿” + for _, ch := range chineseNum { + if val, ok := numberDict[string(ch)]; ok { + temp = val + } else if val, ok := unitDict[string(ch)]; ok { + part += temp * val + temp = 0 + } else if val, ok := largeUnitDict[string(ch)]; ok { + part += temp + temp = 0 + part *= val + } else if val, ok := largerUnitDict[string(ch)]; ok { + part += temp + temp = 0 + part *= val + res += part // 亿之前的数直接加到 res 里 + part = 0 + } + + } + res += part + temp + return res +} +``` + diff --git "a/\345\210\207\346\234\250\345\244\264.md" "b/\345\210\207\346\234\250\345\244\264.md" new file mode 100644 index 0000000..5366f85 --- /dev/null +++ "b/\345\210\207\346\234\250\345\244\264.md" @@ -0,0 +1,69 @@ +#### 问题描述 + +给定长度为n的数组,每个元素代表一个木头的长度,木头可以任意截断,从这堆木头中截出至少k个相同长度为m的木块。已知k,求max(m)。 + +输入两行,第一行n, k,第二行为数组序列。输出最大值。数据保证有解,即结果至少是1。 + +**输入** + +``` +5 5 +4 7 2 10 5 +``` + +**输出** + +``` +4 +``` + +**解释** + +如图,最多可以把它分成5段长度为4的木头 + +![](https://raw.githubusercontent.com/CompetitiveLin/ImageHostingService/picgo/imgs/202409211952291.png) + +--- + +对结果二分:从一到木棍最长的长度进行二分遍历,每次遍历的长度作为 `i`,检查是否可以将所有木头截出来 `k` 个长度为 `i` 的木块 + +```go +package main + +import "fmt" + +func main() { + n, k := 5, 5 + arrays := []int{4, 7, 2, 10, 5} + fmt.Println(wood(n, k, arrays)) +} + +func check(arrays []int, m int) int { + res := 0 + for i := range arrays { + res += arrays[i] / m + } + return res +} + +func wood(n, k int, arrays []int) int { + l, r := 1, 1 + for i := range arrays { + if r < arrays[i] { + r = arrays[i] + } + } + r++ + for l < r { + mid := (l + r + 1) >> 1 + if check(arrays, mid) >= k { + l = mid + } else { + r = mid - 1 + } + } + return l +} + +``` + diff --git "a/\345\240\206.md" "b/\345\240\206.md" new file mode 100644 index 0000000..8a2d32d --- /dev/null +++ "b/\345\240\206.md" @@ -0,0 +1,46 @@ +Go 语言: + +```go +package main + +import ( + "container/heap" + "fmt" + "sort" +) + +type heap2 struct { + sort.IntSlice +} + +func (h heap2) Less(i, j int) bool { // 最小堆:h.IntSlice[i] < h.IntSlice[j];最大堆:h.IntSlice[i] > h.IntSlice[j]; + return h.IntSlice[i] > h.IntSlice[j] +} + +func (h *heap2) Push(v interface{}) { + h.IntSlice = append(h.IntSlice, v.(int)) +} + +func (h *heap2) Pop() interface{} { + temp := h.IntSlice + v := temp[len(temp)-1] + h.IntSlice = temp[:len(temp)-1] + return v +} + +func (h *heap2) push(v int) { + heap.Push(h, v) +} + +func (h *heap2) pop() int { + return heap.Pop(h).(int) +} + +func main() { + q := &heap2{[]int{3, 4, 1, 2, 4, 3}} + heap.Init(q) + fmt.Println(q) +} + +``` + diff --git "a/\345\244\232\347\272\277\347\250\213.md" "b/\345\244\232\347\272\277\347\250\213.md" new file mode 100644 index 0000000..0137fdd --- /dev/null +++ "b/\345\244\232\347\272\277\347\250\213.md" @@ -0,0 +1,116 @@ +# Java + +两个交替打印数字 + +```java +package src.MultiThreading.AlternatePrint; + + +public class AP1 { + + private static int num = 0; + + public static void main(String[] args) { + Object lock = new Object(); + Thread thread1 = new Thread(() -> { + while(true){ + synchronized (lock){ + lock.notify(); + System.out.println("THREAD1 " + num++); + try { + Thread.sleep(1000); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + }); + Thread thread2 = new Thread(() -> { + while(true){ + synchronized (lock){ + lock.notify(); + System.out.println("THREAD2 " + num++); + try { + Thread.sleep(1000); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + }); + thread1.start(); + thread2.start(); + } +} +``` + +三个线程交替打印 + +```java +package src.MultiThreading.AlternatePrint; + +class ThreeThreadAP2 { + + + private static int num = 0; + + private static Object lock = new Object(); + + + public static void main(String[] args) { + + new Thread(() -> { + while(true){ + synchronized (lock){ + if(num % 3 == 0){ + lock.notifyAll(); + System.out.println("Thread1 " + num++); + try { + Thread.sleep(1000); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + } + } + }).start(); + new Thread(() -> { + while(true){ + synchronized (lock){ + if(num % 3 == 1){ + lock.notifyAll(); + System.out.println("Thread2 " + num++); + try { + Thread.sleep(1000); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + }).start(); + new Thread(() -> { + while(true){ + synchronized (lock){ + if(num % 3 == 2){ + lock.notifyAll(); + System.out.println("Thread3 " + num++); + try { + Thread.sleep(1000); + lock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + }).start(); + } +} +``` + diff --git "a/\345\260\217\344\272\216 N \347\232\204\346\234\200\345\244\247\346\225\260.md" "b/\345\260\217\344\272\216 N \347\232\204\346\234\200\345\244\247\346\225\260.md" new file mode 100644 index 0000000..cbb2abe --- /dev/null +++ "b/\345\260\217\344\272\216 N \347\232\204\346\234\200\345\244\247\346\225\260.md" @@ -0,0 +1,152 @@ +### 题目描述 + +数组 nums 中给定可以使用的 1 ~ 9 的数,返回由数组 nums 中的元素组成的小于 n 的最大数。元素可以重复使用。 + +示例 1:nums = {1, 2, 9, 4},n = 2533,返回 2499。 + +示例 2:nums = {1, 2, 5, 4},n = 2543,返回 2542。 + +示例 3:nums = {1, 2, 5, 4},n = 2541,返回 2525。 + +示例 4:nums = {1, 2, 9, 4},n = 2111,返回 1999。 + +示例 5:nums = {5, 9},n = 5555,返回 999。 + +--- + +深度搜索优先:时间复杂度 `O n^len(target)` + +```go +package main + +import ( + "fmt" +) + +var res int + +func main() { + nums := []int{2, 4, 6, 8} + target := 2411 + backtrack(nums, target, 0) + fmt.Println(res) +} + +func backtrack(nums []int, target, sum int) { + if sum >= target { + return + } + res = max(res, sum) + for i := range nums { + backtrack(nums, target, 10*sum+nums[i]) + } +} + +func max(i, j int) int { + if i > j { + return i + } + return j +} + +``` + + + + + +贪心 + 二分查找:时间复杂度 `Ologn` + +```java +package com.example.demo.test; + + +// nums = [5, 4, 8, 2], n = 5416, res = 5288 + +import java.util.Arrays; + + +public class Solution { + + public static void main(String[] args) { + int[] nums = {1,2,5,4}; + int n = 2541; + Arrays.sort(nums); + String s = findMin(n, nums[0]); + StringBuilder sb = new StringBuilder(); + boolean flag = false; + for(int i = 0; i < s.length(); i++){ + /* + 每位都找小于等于该位最大的数,如果s这个位置的数大于等于nums的最大值,就用最大值,否则去数组找最接近的 + 如果当前从数组中找的数字小于s对应这位的数字,此后选择的数字都可以是数组中的最大值 + 例如2513和{2,4,6,8},选到第二位4之后,4比5小,此后就可以都选择8,组成2488 + */ + char c = s.charAt(i); + if(flag){ + sb.append(nums[nums.length - 1]); + } else { + int index = getIndex(nums, i, s); + sb.append(nums[index]); + if(nums[index] < c - '0') flag = true; + } + } + System.out.println(sb); + } + + public static int getIndex(int[] nums, int index, String s){ + int target = s.charAt(index) - '0'; + /* + 此外,选择数字还要受到下一位数字的影响 + 假设s为2411,nums为{2,4,6,8} + 第二位数字应该选4,但如果选了4,后面再拼接就一定会大于s了。 + */ + if(index < s.length() - 1){ + int next = s.charAt(index + 1) - '0'; + if(next <= nums[0]){ + target -= 1; + } + } + return dichotomy(nums, target); + } + + + /** + * 二分查找,查找小于等于target的最大下标。 + * @param nums + * @param target + * @return + */ + public static int dichotomy(int[] nums, int target){ + int left = 0, right = nums.length; + while(left < right){ + int mid = (left + right) / 2; + if(nums[mid] < target){ + left = mid + 1; + } else if(nums[mid] > target){ + right = mid; + } else return mid; + } + return left - 1; + } + + /** + * 判断是否能拼出和 n 长度一样的字符串,通过判断str的第一位与nums[0](minValue)的大小关系,无法判断则迭代判断下一位 + * @param n + * @param minValue + * @return + */ + public static String findMin(int n, int minValue){ + boolean flag = false; + String numStr = String.valueOf(n); + for(int i = 0; i < numStr.length(); i++){ + if(minValue < (numStr.charAt(i) - '0')){ + flag = true; + break; + } + } + int maxValue = flag ? (n - 1) : (int)(Math.pow(10, numStr.length() - 1) - 1); + return String.valueOf(maxValue); + } +} + +``` diff --git "a/\345\271\266\346\237\245\351\233\206\357\274\210Union-Find\357\274\211.md" "b/\345\271\266\346\237\245\351\233\206\357\274\210Union-Find\357\274\211.md" new file mode 100644 index 0000000..ebd9483 --- /dev/null +++ "b/\345\271\266\346\237\245\351\233\206\357\274\210Union-Find\357\274\211.md" @@ -0,0 +1,34 @@ +# 并查集(Union-Find) + +一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。不交集指的是一系列没有重复元素的集合。主要是支持两种操作: + +- **合并(Union)**:将两个集合合并成一个集合。 +- **查找(Find)**:确定某个元素属于哪个集合。通常是返回集合内的一个「代表元素」。 + +```go +type unionFind struct { + father []int +} + +func (u unionFind) init(nums []int) { + for i := range nums { + u.father[i] = i + } +} + +func (u unionFind) find(x int) int { + if u.father[x] == x { + return x + } + root := u.find(u.father[x]) // 状态压缩,找到根节点,而不是一层一层找父节点 + u.father[x] = root + return root +} +func (u unionFind) union(x, y int) { + xRoot, yRoot := u.find(x), u.find(y) + if xRoot != yRoot { + u.father[yRoot] = xRoot + } +} +``` + diff --git "a/\346\211\213\345\206\231\345\240\206.md" "b/\346\211\213\345\206\231\345\240\206.md" new file mode 100644 index 0000000..2995150 --- /dev/null +++ "b/\346\211\213\345\206\231\345\240\206.md" @@ -0,0 +1,169 @@ + + +# 小顶堆(Go) + +建堆有两种方式: + +1. 自顶向下,从第一个节点开始到最后一个节点遍历,按插入节点的方式建堆,节点的交换是从下往上的,时间复杂度是 `O(nlogn)` +2. 自底向上,从倒数第一个非叶子节点开始,到第一个节点,节点的交换是从上往下的,时间复杂度是 `O(n)` + +堆的操作: + +1. 插入节点:将节点插入到数组尾,用将数组的尾节点从下往上的交换方式一直往上交换 + +2. 弹出堆顶:交换堆顶节点和数组最后一个节点,删除数组最后一个节点,接着将堆顶节点用从上往下的方式一直往下交换 + +```go +package main + +import "fmt" + +func main() { + h := heap{4, 6, 9, 1, 2, 3, 12, 34, 5, 6} + h.init1() + fmt.Println(h) + h.insert(0) + fmt.Println(h) + h.pop() + fmt.Println(h) +} + +type heap []int + +func (h heap) swap(i, j int) { + h[i], h[j] = h[j], h[i] +} + +func (h heap) init1() { // bottom-up, O(n), 自底向上的建堆方式,节点的交换是从上往下的 + for i := len(h)/2 - 1; i >= 0; i-- { + h.topDown(i) + } +} + +func (h heap) init2() { // top-down O(nlogn) 自顶向下的建堆方式,节点的交换是从下往上的 + for i := 0; i < len(h); i++ { + h.bottomUp(i) + } +} + +func (h *heap) insert(val int) { + *h = append(*h, val) + h.bottomUp(len(*h) - 1) +} + +func (h heap) bottomUp(cur int) { + parent := (cur+1)/2 - 1 + for parent >= 0 { + if h[cur] < h[parent] { + h.swap(cur, parent) + } else { + break + } + cur = parent + parent = (parent+1)/2 - 1 + } +} + +func (h heap) topDown(cur int) { + minn := 2*cur + 1 + for minn < len(h) { + if 2*cur+2 < len(h) && h[2*cur+2] < h[minn] { + minn = 2*cur + 2 + } + if h[cur] > h[minn] { + h.swap(cur, minn) + } else { + break + } + cur = minn + minn = 2*cur + 1 + } +} + +func (h *heap) pop() int { + h.swap(0, len(*h)-1) + res := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + h.topDown(0) + return res +} + +``` + + + + + +# 大顶堆(Java) + +从第一个非叶子节点(若数组长度为 `size`,那么第一个非叶子节点的下标则为 `size / 2` )倒序遍历至第一个节点,每个循环中都与自身子节点中较大的子节点交换。 + +```java +package src.DataStructure; + +public class Heap { + + private int[] arrays = new int[1000]; + + private int size; + + public Heap(int[] arrays){ + this.size = arrays.length; + System.arraycopy(arrays, 0, this.arrays, 0, size); + heapify(size); + } + + public void add(int value){ + arrays[size++] = value; + heapify(size); + } + + + public void heapify(int size){ + for(int i = size / 2; i >= 0; i--){ + sink(i); + } + } + + public void sink(int index){ + while(2 * index + 1 < size){ + int temp = 2 * index + 1; + if(temp + 1 < size && arrays[temp] < arrays[temp + 1]){ + temp = temp + 1; + } + if(arrays[temp] <= arrays[index]){ + break; + } + swap(arrays, index, temp); + index = temp; + } + } + + public int poll(){ + int ans = arrays[0]; + swap(arrays, 0, --size); + heapify(size); + return ans; + } + + public void swap(int[] nums, int left, int right){ + int temp = nums[left]; + nums[left] = nums[right]; + nums[right] = temp; + } + + + public static void main(String[] args) { + int[] arrays = new int[]{6, 8, 10, 2, 4, 11, 13, 9, 1, 5, 7, 3}; + Heap heap = new Heap(arrays); + heap.add(200); + while(heap.size != 0){ + System.out.println(heap.poll()); + } + } +} + +``` + + + diff --git "a/\346\261\202\345\271\263\346\226\271\346\240\271.md" "b/\346\261\202\345\271\263\346\226\271\346\240\271.md" new file mode 100644 index 0000000..a3da4ed --- /dev/null +++ "b/\346\261\202\345\271\263\346\226\271\346\240\271.md" @@ -0,0 +1,58 @@ +#### 求平方根 + +二分查找: + +因为查找的过程中是线性增加的,所以应该想到二分。判断左右节点之差是否小于 `diff`,接着继续二分左右节点之和,再根据其平方是否大于给定数移动左节点或右节点。 + +```java +package src.Algorithm; + +public class SquareRoot { + + public static double squareRoot(int num){ + double diff = 1e-9; + double left = 1.0, right = num * 1.0; + while(right - left > diff){ + double mid = (right + left) / 2.0; + if(mid * mid > num){ + right = mid; + } else { + left = mid; + } + } + String s = left + ""; + return Double.parseDouble(s.substring(0, 11)); + } + + public static void main(String[] args) { + System.out.println(squareRoot(3)); + } +} + +``` + + + +Go 语言: + +```go +package main + +import "fmt" + +func main() { + num := 8 + target, left, right := float64(num), float64(0), float64(num) + for right-left > 1e-7 { + mid := (left + right) / 2 + if mid*mid > target { + right = mid + } else { + left = mid + } + } + fmt.Println(left) + fmt.Println(right) +} +``` + diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 0000000..5c68deb --- /dev/null +++ "b/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,122 @@ +# 设计模式 + + + +## 责任链模式 + +```go +package main + +import "fmt" + +type person struct { +} + +type chain interface { + check(person) + setNext(chain) +} + +type check1 struct { + next chain +} + +func (c check1) check(p person) { + fmt.Println("check1") + c.next.check(p) +} + +func (c check1) setNext(ch chain) { + c.next = ch +} + +type check2 struct { + next chain +} + +func (c check2) check(p person) { + fmt.Println("check2") +} + +func (c check2) setNext(ch chain) { + c.next = ch +} + +func main() { + p := person{} + + c1 := &check1{next: check2{}} + c1.check(p) +} + +``` + + + + + +## 单例模式 + +```go +package main + +import "sync" + +type people struct { +} + +var instance *people +var once sync.Once + +func getInstance() *people { + once.Do(func() { + instance = &people{} + }) + return instance +} + +func main() { + +} +``` + + + +```java +public class DoubleCheckedLock { + + // 使用volatile修饰禁止重排序 + private volatile static DoubleCheckedLock instance; + + private DoubleCheckedLock() { + //构造器必须私有 不然直接new就可以创建 + } + + + public static DoubleCheckedLock getInstance() { + //第一次判断,假设会有好多线程,如果doubleLock没有被实例化,那么就会到下一步获取锁,只有一个能获取到, + //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程 + if (instance == null) { + //同步 + synchronized (DoubleCheckedLock.class) { + //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断doubleLock是null, + // 他就实例化了doubleLock,然后他出了锁,这时候线程B经过等待A释放的锁,B获取锁了, + // 如果没有第二个判断,那么他还是会去new DoubleLock(),再创建一个实例,所以为了防止这种情况,需要第二次判断 + if (instance == null) { + //下面这句代码其实分为三步: + //1.开辟内存分配给这个对象 + //2.初始化对象 + //3.将内存地址赋给虚拟机栈内存中的doubleLock变量 + //注意上面这三步,第2步和第3步的顺序是随机的,这是计算机指令重排序的问题 + //假设有两个线程,其中一个线程执行下面这行代码,如果第三步先执行了,就会把没有初始化的内存赋值给doubleLock + //然后恰好这时候有另一个线程执行了第一个判断if(doubleLock == null),然后就会发现doubleLock指向了一个内存地址 + //这另一个线程就直接返回了这个没有初始化的内存,所以要防止第2步和第3步重排序 + instance = new DoubleCheckedLock(); + } + } + } + return instance; + } +} +``` +