这篇文章主要是记录刷过的题以及里面一些需要注意的关键点(https://www.programmercarl.com/)

1.做题记录

序号 题目 日期 类型 链接
1 704.二分查找 2024年7月5日 数组 https://leetcode.cn/problems/binary-search/description/
2 27.移除元素 2024年7月5日 数组 https://leetcode.cn/problems/remove-element/description/
3 977.有序数组的平方 2024年7月5日 数组 https://leetcode.cn/problems/squares-of-a-sorted-array/description/
4 209.长度最小的子数组 2024年7月5日 数组 https://leetcode.cn/problems/minimum-size-subarray-sum/description/
5 59.螺旋矩阵II 2024年7月5日 数组 https://leetcode.cn/problems/spiral-matrix-ii/description/
6 203.移除链表元素 2024年7月6日 链表 https://leetcode.cn/problems/remove-linked-list-elements/description/
7 206.反转链表 2024年7月6日 链表 https://leetcode.cn/problems/reverse-linked-list/description/
8 24. 两两交换链表中的节点 2024年7月6日 链表 https://leetcode.cn/problems/swap-nodes-in-pairs/description/
9 19.删除链表的倒数第N个节点 2024年7月6日 链表 https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
10 160. 相交链表 2024年7月6日 链表 https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
11 142.环形链表II 2024年7月6日 链表 https://leetcode.cn/problems/linked-list-cycle-ii/description/
12 242.有效的字母异位词 2024年7月7日 哈希表 https://leetcode.cn/problems/valid-anagram/description/
13 349. 两个数组的交集 2024年7月7日 哈希表 https://leetcode.cn/problems/intersection-of-two-arrays/description/
14 202.快乐数 2024年7月7日 哈希表 https://leetcode.cn/problems/happy-number/description/
15 1.两数之和 2024年7月7日 哈希表 https://leetcode.cn/problems/two-sum/description/
16 383. 赎金信 2024年7月7日 哈希表 https://leetcode.cn/problems/ransom-note/description/
17 15.三数之和 2024年7月7日 哈希表 https://leetcode.cn/problems/3sum/description/
18 18.四数之和 2024年7月8日 哈希表 https://leetcode.cn/problems/4sum/description/
19 454.四数相加II 2024年7月8日 哈希表 https://leetcode.cn/problems/4sum-ii/description/
20 344.反转字符串 2024年7月8日 字符串 https://leetcode.cn/problems/reverse-string/description/
21 541. 反转字符串II 2024年7月8日 字符串 https://leetcode.cn/problems/reverse-string-ii/description/
22 509. 斐波那契数 2024年7月8日 动态规划 https://leetcode.cn/problems/fibonacci-number/description/
23 70. 爬楼梯 2024年7月8日 动态规划 https://leetcode.cn/problems/climbing-stairs/description/
24 746. 使用最小花费爬楼梯 2024年7月8日 动态规划 https://leetcode.cn/problems/min-cost-climbing-stairs/description/
25 62.不同路径 2024年7月8日 动态规划 https://leetcode.cn/problems/unique-paths/description/
26 63. 不同路径 II 2024年7月8日 动态规划 https://leetcode.cn/problems/unique-paths-ii/description/
27 343. 整数拆分 2024年7月8日 动态规划 https://leetcode.cn/problems/integer-break/description/
28 96.不同的二叉搜索树 2024年7月8日 动态规划 https://leetcode.cn/problems/unique-binary-search-trees/description/
29 416. 分割等和子集 2024年7月8日 动态规划 https://leetcode.cn/problems/partition-equal-subset-sum/description/
30 279.完全平方数 2024年7月8日 动态规划 https://leetcode.cn/problems/perfect-squares/description/
31 121. 买卖股票的最佳时机 2024年7月8日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
32 122.买卖股票的最佳时机II 2024年7月8日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
33 123.买卖股票的最佳时机III 2024年7月8日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/description/
34 188.买卖股票的最佳时机IV 2024年7月8日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/
35 714.买卖股票的最佳时机含手续费 2024年7月8日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/
36 647. 回文子串 2024年7月9日 动态规划 https://leetcode.cn/problems/palindromic-substrings/description/
37 198.打家劫舍 2024年7月10日 动态规划 https://leetcode.cn/problems/house-robber/description/
38 213.打家劫舍II 2024年7月10日 动态规划 https://leetcode.cn/problems/house-robber-ii/description/
39 337.打家劫舍 III 2024年7月10日 动态规划 https://leetcode.cn/problems/house-robber-iii/description/
40 20. 有效的括号 2024年7月10日 栈与队列 https://leetcode.cn/problems/valid-parentheses/description/
41 1047. 删除字符串中的所有相邻重复项 2024年7月10日 栈与队列 https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/description/
42 150. 逆波兰表达式求值 2024年7月10日 栈与队列 https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/
43 347.前 K 个高频元素 2024年7月10日 栈与队列 https://leetcode.cn/problems/top-k-frequent-elements/description/
44 455.分发饼干 2024年7月10日 贪心算法 https://leetcode.cn/problems/assign-cookies/description/
45 376. 摆动序列 2024年7月10日 动态规划 https://leetcode.cn/problems/wiggle-subsequence/description/
46 53. 最大子序和 2024年7月10日 动态规划 https://leetcode.cn/problems/maximum-subarray/description/
47 300.最长递增子序列 2024年7月10日 动态规划 https://leetcode.cn/problems/longest-increasing-subsequence/description/
48 674. 最长连续递增序列 2024年7月10日 动态规划 https://leetcode.cn/problems/longest-continuous-increasing-subsequence/description/
49 1365.有多少小于当前数字的数字 2024年7月10日 数组 https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/description/
50 941.有效的山脉数组 2024年7月10日 数组 https://leetcode.cn/problems/valid-mountain-array/description/
51 1207.独一无二的出现次数 2024年7月11日 数组 https://leetcode.cn/problems/unique-number-of-occurrences/description/
52 283. 移动零 2024年7月11日 数组 https://leetcode.cn/problems/move-zeroes/description/
53 189. 旋转数组 2024年7月11日 数组 https://leetcode.cn/problems/rotate-array/description/
54 724.寻找数组的中心下标 2024年7月11日 数组 https://leetcode.cn/problems/find-pivot-index/description/
55 922. 按奇偶排序数组II 2024年7月11日 数组 https://leetcode.cn/problems/sort-array-by-parity-ii/description/
56 1356. 根据数字二进制下 1 的数目排序 2024年7月12日 数学 https://leetcode.cn/problems/sort-integers-by-the-number-of-1-bits/description/
57 77.组合 2024年7月12日 回溯算法 https://leetcode.cn/problems/combinations/description/
58 216.组合总和III 2024年7月13日 回溯算法 https://leetcode.cn/problems/combination-sum-iii/description/
59 17.电话号码的字母组合 2024年7月15日 回溯算法 https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/
60 131.分割回文串 2024年7月15日 回溯算法 https://leetcode.cn/problems/palindrome-partitioning/description/
61 860.柠檬水找零 2024年7月15日 贪心算法 https://leetcode.cn/problems/lemonade-change/description/
62 135. 分发糖果 2024年7月15日 贪心算法 https://leetcode.cn/problems/candy/description/
63 55. 跳跃游戏 2024年7月16日 贪心算法 https://leetcode.cn/problems/jump-game/description/
64 1005.K次取反后最大化的数组和 2024年7月16日 贪心算法 https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/description/
65 452. 用最少数量的箭引爆气球 2024年7月17日 贪心算法 https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/description/
66 435. 无重叠区间 2024年7月17日 贪心算法 https://leetcode.cn/problems/non-overlapping-intervals/description/
67 763.划分字母区间 2024年7月17日 贪心算法 https://leetcode.cn/problems/partition-labels/description/
68 1143.最长公共子序列 2024年7月19日 动态规划 https://leetcode.cn/problems/longest-common-subsequence/description/
69 1035.不相交的线 2024年7月19日 动态规划 https://leetcode.cn/problems/uncrossed-lines/description/
70 718. 最长重复子数组 2024年7月19日 动态规划 https://leetcode.cn/problems/maximum-length-of-repeated-subarray/description/
71 1049.最后一块石头的重量II 2024年8月2日 动态规划 https://leetcode.cn/problems/last-stone-weight-ii/description/
72 494.目标和 2024年8月3日 动态规划 https://leetcode.cn/problems/target-sum/description/
73 474.一和零 2024年8月3日 动态规划 https://leetcode.cn/problems/ones-and-zeroes/description/
74 518.零钱兑换II 2024年8月3日 动态规划 https://leetcode.cn/problems/coin-change-ii/description/
75 377. 组合总和 Ⅳ 2024年8月4日 动态规划 https://leetcode.cn/problems/combination-sum-iv/description/
76 322. 零钱兑换 2024年8月4日 动态规划 https://leetcode.cn/problems/coin-change/description/
77 309.最佳买卖股票时机含冷冻期 2024年8月4日 动态规划 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/
78 392.判断子序列 2024年8月4日 动态规划 https://leetcode.cn/problems/is-subsequence/description/
79 115.不同的子序列 2024年8月5日 动态规划 https://leetcode.cn/problems/distinct-subsequences/description/
80 583. 两个字符串的删除操作 2024年8月6日 动态规划 https://leetcode.cn/problems/delete-operation-for-two-strings/description/
81 72. 编辑距离 2024年8月6日 动态规划 https://leetcode.cn/problems/edit-distance/description/
82 516.最长回文子序列 2024年8月7日 动态规划 https://leetcode.cn/problems/longest-palindromic-subsequence/description/
83 739. 每日温度 2024年8月7日 单调栈 https://leetcode.cn/problems/daily-temperatures/description/
84 496.下一个更大元素 I 2024年8月16日 单调栈 https://leetcode.cn/problems/next-greater-element-i/description/
85 503.下一个更大元素II 2024年8月16日 单调栈 https://leetcode.cn/problems/next-greater-element-ii/description/
86 42. 接雨水 2024年8月16日 双指针+动态规划 https://leetcode.cn/problems/trapping-rain-water/description/
87 84.柱状图中最大的矩形 2024年8月17日 双指针+单调栈 https://leetcode.cn/problems/largest-rectangle-in-histogram/description/
88 39. 组合总和 2024年8月17日 回溯算法 https://leetcode.cn/problems/combination-sum/description/
89 40.组合总和II 2024年8月17日 回溯算法 https://leetcode.cn/problems/combination-sum-ii/description/
90 93.复原IP地址 2024年8月17日 回溯算法 https://leetcode.cn/problems/restore-ip-addresses/description/
91 78.子集 2024年8月17日 回溯算法 https://leetcode.cn/problems/subsets/description/
92 90.子集II 2024年8月17日 回溯算法 https://leetcode.cn/problems/subsets-ii/description/
93 491.递增子序列 2024年8月18日 回溯算法 https://leetcode.cn/problems/non-decreasing-subsequences/description/
94 46.全排列 2024年8月18日 回溯算法 https://leetcode.cn/problems/permutations/description/
95 47.全排列 II 2024年8月18日 回溯算法 https://leetcode.cn/problems/permutations-ii/description/
96 37. 解数独 2024年8月18日 回溯算法 https://leetcode.cn/problems/sudoku-solver/description/
97 51. N皇后 2024年8月19日 回溯算法 https://leetcode.cn/problems/n-queens/description/
98 332.重新安排行程 2024年8月19日 回溯算法 https://leetcode.cn/problems/reconstruct-itinerary/description/
99 56. 合并区间 2024年8月19日 贪心算法 https://leetcode.cn/problems/merge-intervals/description/
100 738.单调递增的数字 2024年8月19日 贪心算法 https://leetcode.cn/problems/monotone-increasing-digits/description/
101 968.监控二叉树 2024年8月20日 贪心算法 https://leetcode.cn/problems/binary-tree-cameras/description/
102 94.二叉树的中序遍历 2024年8月21日 二叉树 https://leetcode.cn/problems/binary-tree-inorder-traversal/description/
103 144.二叉树的前序遍历 2024年8月21日 二叉树 https://leetcode.cn/problems/binary-tree-preorder-traversal/description/
104 145.二叉树的后序遍历 2024年8月21日 二叉树 https://leetcode.cn/problems/binary-tree-postorder-traversal/description/
105 102.二叉树的层序遍历 2024年8月21日 二叉树 https://leetcode.cn/problems/binary-tree-level-order-traversal/description/
106 107. 二叉树的层序遍历 II 2024年8月22日 二叉树 https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/description/
107 199. 二叉树的右视图 2024年8月22日 二叉树 https://leetcode.cn/problems/binary-tree-right-side-view/description/
108 637. 二叉树的层平均值 2024年8月22日 二叉树 https://leetcode.cn/problems/average-of-levels-in-binary-tree/description/
109 429. N 叉树的层序遍历 2024年8月22日 二叉树 https://leetcode.cn/problems/n-ary-tree-level-order-traversal/description/
110 515. 在每个树行中找最大值 2024年8月22日 二叉树 https://leetcode.cn/problems/find-largest-value-in-each-tree-row/description/
111 116.填充每个节点的下一个右侧节点指针 2024年8月23日 二叉树 https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/description/
112 117. 填充每个节点的下一个右侧节点指针 II 2024年8月23日 二叉树 https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/description/
113 104. 二叉树的最大深度 2024年8月23日 二叉树 https://leetcode.cn/problems/maximum-depth-of-binary-tree/description/
114 111. 二叉树的最小深度 2024年8月23日 二叉树 https://leetcode.cn/problems/minimum-depth-of-binary-tree/description/
115 226. 翻转二叉树 2024年8月23日 二叉树 https://leetcode.cn/problems/invert-binary-tree/description/
116 101. 对称二叉树 2024年8月23日 二叉树 https://leetcode.cn/problems/symmetric-tree/description/
117 559. N 叉树的最大深度 2024年8月23日 二叉树 https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/description/
118 222. 完全二叉树的节点个数 2024年8月23日 二叉树 https://leetcode.cn/problems/count-complete-tree-nodes/description/
119 257. 二叉树的所有路径 2024年8月23日 二叉树 https://leetcode.cn/problems/binary-tree-paths/description/
120 404. 左叶子之和 2024年8月23日 二叉树 https://leetcode.cn/problems/sum-of-left-leaves/description/
121 513. 找树左下角的值 2024年8月23日 二叉树 https://leetcode.cn/problems/find-bottom-left-tree-value/description/
122 112. 路径总和 2024年8月23日 二叉树 https://leetcode.cn/problems/path-sum/description/
123 110. 平衡二叉树 2024年8月24日 二叉树 https://leetcode.cn/problems/balanced-binary-tree/description/
124 617. 合并二叉树 2024年8月24日 二叉树 https://leetcode.cn/problems/merge-two-binary-trees/description/
125 236. 二叉树的最近公共祖先 2024年8月25日 二叉树 https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/
126 501. 二叉搜索树中的众数 2024年8月26日 二叉树 https://leetcode.cn/problems/find-mode-in-binary-search-tree/description/
127 530. 二叉搜索树的最小绝对差 2024年8月26日 二叉树 https://leetcode.cn/problems/minimum-absolute-difference-in-bst/description/
128 98. 验证二叉搜索树 2024年8月29日 二叉树 https://leetcode.cn/problems/validate-binary-search-tree/description/
129 235. 二叉搜索树的最近公共祖先 2024年8月29日 二叉树 https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/description/
130 701. 二叉搜索树中的插入操作 2024年9月3日 二叉树 https://leetcode.cn/problems/insert-into-a-binary-search-tree/description/
131 538. 把二叉搜索树转换为累加树 2024年9月4日 二叉树 https://leetcode.cn/problems/convert-bst-to-greater-tree/description/
132 108. 将有序数组转换为二叉搜索树 2024年9月4日 二叉树 https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/description/
133 106. 从中序与后序遍历序列构造二叉树 2024年9月4日 二叉树 https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/
134 105. 从前序与中序遍历序列构造二叉树 2024年9月4日 二叉树 https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/
135 700. 二叉搜索树中的搜索 2024年9月4日 二叉树 https://leetcode.cn/problems/search-in-a-binary-search-tree/description/
136 654. 最大二叉树 2024年9月5日 二叉树 https://leetcode.cn/problems/maximum-binary-tree/description/
137 655. 输出二叉树 2024年9月5日 二叉树 https://leetcode.cn/problems/print-binary-tree/description/
138 450.删除二叉搜索树中的节点 2024年9月6日 二叉树 https://leetcode.cn/problems/delete-node-in-a-bst/description/
139 669. 修剪二叉搜索树 2024年9月9日 二叉树 https://leetcode.cn/problems/trim-a-binary-search-tree/description/
140 2. 两数相加 2024年9月10日 链表 https://leetcode.cn/problems/add-two-numbers/description/
141 7. 整数反转 2024年9月10日 数学 https://leetcode.cn/problems/reverse-integer/description/
142 114. 二叉树展开为链表 2024年9月10日 二叉树 https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/description/
143 136. 只出现一次的数字 2024年9月11日 数学 https://leetcode.cn/problems/single-number/description/
144 129. 求根节点到叶节点数字之和 2024年9月11日 二叉树 https://leetcode.cn/problems/sum-root-to-leaf-numbers/description/
145 165. 比较版本号 2024年9月11日 字符串 https://leetcode.cn/problems/compare-version-numbers/description/
146 461. 汉明距离 2024年9月11日 数学 https://leetcode.cn/problems/hamming-distance/description/
147 75. 颜色分类 2024年9月11日 数组 https://leetcode.cn/problems/sort-colors/description/
148 64. 最小路径和 2024年9月11日 动态规划 https://leetcode.cn/problems/minimum-path-sum/description/
149 338. 比特位计数 2024年9月11日 数学 https://leetcode.cn/problems/counting-bits/description/
150 26. 删除有序数组中的重复项 2024年9月11日 数组 https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/
151 66. 加一 2024年9月11日 数学 https://leetcode.cn/problems/plus-one/description/
152 103. 二叉树的锯齿形层序遍历 2024年9月12日 二叉树 https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/description/
153 13. 罗马数字转整数 2024年9月12日 字符串 https://leetcode.cn/problems/roman-to-integer/description/
154 448. 找到所有数组中消失的数字 2024年9月14日 数组 https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/description/
155 543. 二叉树的直径 2024年9月14日 二叉树 https://leetcode.cn/problems/diameter-of-binary-tree/description/
156 35. 搜索插入位置 2024年9月15日 数组 https://leetcode.cn/problems/search-insert-position/description/?envType=study-plan-v2&envId=top-100-liked
157 287. 寻找重复数 2024年9月15日 数组 https://leetcode.cn/problems/find-the-duplicate-number/description/?envType=study-plan-v2&envId=top-100-liked
158 73. 矩阵置零 2024年9月15日 数组 https://leetcode.cn/problems/set-matrix-zeroes/description/?envType=study-plan-v2&envId=top-100-liked
159 148. 排序链表 2024年9月15日 链表 https://leetcode.cn/problems/sort-list/description/?envType=study-plan-v2&envId=top-100-liked
160 230. 二叉搜索树中第 K 小的元素 2024年9月16日 二叉树 https://leetcode.cn/problems/kth-smallest-element-in-a-bst/description/?envType=study-plan-v2&envId=top-100-liked
161 437. 路径总和 III 2024年9月16日 二叉树 https://leetcode.cn/problems/path-sum-iii/description/?envType=study-plan-v2&envId=top-100-liked
162 79. 单词搜索 2024年9月17日 回溯算法 https://leetcode.cn/problems/word-search/description/?envType=study-plan-v2&envId=top-100-liked
163 74. 搜索二维矩阵 2024年9月17日 数组 https://leetcode.cn/problems/search-a-2d-matrix/description/?envType=study-plan-v2&envId=top-100-liked
164 34. 在排序数组中查找元素的第一个和最后一个位置 2024年9月17日 数组 https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envType=study-plan-v2&envId=top-100-liked
165 146. LRU 缓存 2024年9月17日 哈希表 https://leetcode.cn/problems/lru-cache/description/?envType=study-plan-v2&envId=top-100-liked
166 45. 跳跃游戏 II 2024年9月18日 动态规划 https://leetcode.cn/problems/jump-game-ii/description/?envType=study-plan-v2&envId=top-100-liked
167 23. 合并 K 个升序链表 2024年9月18日 链表 https://leetcode.cn/problems/merge-k-sorted-lists/description/?envType=study-plan-v2&envId=top-100-liked
168 560.和为 K 的子数组 2024年9月19日 动态规划 https://leetcode.cn/problems/subarray-sum-equals-k/description/?envType=study-plan-v2&envId=top-100-liked
169 139. 单词拆分 2024年9月20日 动态规划 https://leetcode.cn/problems/word-break/description/?envType=study-plan-v2&envId=top-100-liked
170 5. 最长回文子串 2024年9月21日 动态规划 https://leetcode.cn/problems/longest-palindromic-substring/description/?envType=study-plan-v2&envId=top-100-liked
171 3. 无重复字符的最长子串 2024年9月21日 双指针+数组 https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/?envType=study-plan-v2&envId=top-100-liked
172 438. 找到字符串中所有字母异位词 2024年9月21日 哈希表 https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/?envType=study-plan-v2&envId=top-100-liked
173 153. 寻找旋转排序数组中的最小值 2024年9月22日 数组 https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/description/?envType=study-plan-v2&envId=top-100-liked
174 33. 搜索旋转排序数组 2024年9月23日 数组 https://leetcode.cn/problems/search-in-rotated-sorted-array/description/?envType=study-plan-v2&envId=top-100-liked
175 54. 螺旋矩阵 2024年9月24日 数组 https://leetcode.cn/problems/spiral-matrix/description/?envType=study-plan-v2&envId=top-100-liked
176 48. 旋转图像 2024年9月25日 数组 https://leetcode.cn/problems/rotate-image/description/?envType=study-plan-v2&envId=top-100-liked
177 240. 搜索二维矩阵 II 2024年9月26日 数组 https://leetcode.cn/problems/search-a-2d-matrix-ii/description/?envType=study-plan-v2&envId=top-100-liked
178 31. 下一个排列 2024年9月28日 数组 https://leetcode.cn/problems/next-permutation/description/?envType=study-plan-v2&envId=top-100-liked
179 221. 最大正方形 2024年10月13日 动态规划 https://leetcode.cn/problems/maximal-square/description/?envType=study-plan-v2&envId=dynamic-programming
180 394. 字符串解码 2024年10月14日 栈 https://leetcode.cn/problems/decode-string/description/?envType=study-plan-v2&envId=top-100-liked
181 138. 随机链表的复制 2024年10月15日 链表 https://leetcode.cn/problems/copy-list-with-random-pointer/description/?envType=study-plan-v2&envId=top-100-liked
182 1137. 第 N 个泰波那契数 2024年10月15日 动态规划 https://leetcode.cn/problems/n-th-tribonacci-number/description/?envType=study-plan-v2&envId=dynamic-programming
183 120. 三角形最小路径和 2024年10月16日 动态规划 https://leetcode.cn/problems/triangle/description/?envType=study-plan-v2&envId=dynamic-programming
184 931. 下降路径最小和 2024年10月16日 动态规划 https://leetcode.cn/problems/minimum-falling-path-sum/description/?envType=study-plan-v2&envId=dynamic-programming
185 673. 最长递增子序列的个数 2024年10月17日 动态规划 https://leetcode.cn/problems/number-of-longest-increasing-subsequence/description/?envType=study-plan-v2&envId=dynamic-programming
186 95. 不同的二叉搜索树 II 2024年10月18日 二叉树 https://leetcode.cn/problems/unique-binary-search-trees-ii/description/?envType=study-plan-v2&envId=dynamic-programming
187 91. 解码方法 2024年10月18日 动态规划 https://leetcode.cn/problems/decode-ways/description/?envType=study-plan-v2&envId=dynamic-programming
188 1218. 最长定差子序列 2024年10月19日 动态规划 https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/description/?envType=study-plan-v2&envId=dynamic-programming
189 646. 最长数对链 2024年10月19日 动态规划 https://leetcode.cn/problems/maximum-length-of-pair-chain/description/?envType=study-plan-v2&envId=dynamic-programming
190 2466. 统计构造好字符串的方案数 2024年10月20日 动态规划 https://leetcode.cn/problems/count-ways-to-build-good-strings/description/?envType=study-plan-v2&envId=dynamic-programming
191 740. 删除并获得点数 2024年10月20日 动态规划 https://leetcode.cn/problems/delete-and-earn/description/?envType=study-plan-v2&envId=dynamic-programming
192 790. 多米诺和托米诺平铺 2024年10月21日 动态规划 https://leetcode.cn/problems/domino-and-tromino-tiling/description/?envType=study-plan-v2&envId=dynamic-programming
193 712. 两个字符串的最小ASCII删除和 2024年10月22日 动态规划 https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/description/?envType=study-plan-v2&envId=dynamic-programming
194 983. 最低票价 2024年10月23日 动态规划 https://leetcode.cn/problems/minimum-cost-for-tickets/description/?envType=study-plan-v2&envId=dynamic-programming
195 872. 叶子相似的树 2024年10月23日 二叉树 https://leetcode.cn/problems/leaf-similar-trees/?envType=study-plan-v2&envId=leetcode-75
196 643. 子数组最大平均数 I 2024年10月23日 数组 https://leetcode.cn/problems/maximum-average-subarray-i/description/?envType=study-plan-v2&envId=leetcode-75
197 345. 反转字符串中的元音字母 2024年10月23日 字符串 https://leetcode.cn/problems/reverse-vowels-of-a-string/description/?envType=study-plan-v2&envId=leetcode-75
198 1431. 拥有最多糖果的孩子 2024年10月24日 数组 https://leetcode.cn/problems/kids-with-the-greatest-number-of-candies/description/?envType=study-plan-v2&envId=leetcode-75
199 605. 种花问题 2024年10月24日 数组 https://leetcode.cn/problems/can-place-flowers/description/?envType=study-plan-v2&envId=leetcode-75
200 2215. 找出两数组的不同 2024年10月24日 数组 https://leetcode.cn/problems/find-the-difference-of-two-arrays/description/?envType=study-plan-v2&envId=leetcode-75

2.一些java用法

put+remove(HashMap),add+remove(List)

数学函数

平方根函数:

1
double f = Math.sqrt(n);

数组

数组排序:

1
Arrays.sort(nums);

打印数组:

1
System.out.println(Arrays.toString(array));

数组初始化:

1
Arrays.fill(nums, 100);

数组获得复制数组(并不指向同一地址):

1
int[] num = Arrays.copyOf(nums, len);

int[]数组自定义排序规则:

1
2
3
4
5
6
7
8
return Arrays.stream(arr).boxed().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer num1, Integer num2) {
int a = count1(num1);
int b = count1(num2);
return (a == b) ? Integer.compare(num1, num2) : Integer.compare(a, b);
}
}).mapToInt(Integer::intValue).toArray();

这段代码使用了Java 8及以上版本的Stream API来处理一个整数数组(假设这个数组名为arr),尽管初始代码段中并没有直接显示arr的声明和类型,但从上下文可以推断出arr是一个int[]类型的数组。接下来,我将逐步解释这段代码的作用:

  1. Arrays.stream(arr).boxed():
    • Arrays.stream(arr)将int[]数组arr转换为一个IntStream,这是Java Stream API的一部分,专门用于处理基本数据类型int的序列。
    • .boxed()方法将IntStream转换为Stream<Integer>。这是因为IntStream处理的是基本类型int,而Stream<Integer>处理的是对象类型Integer。这一步骤是必要的,因为接下来的排序和比较操作需要Integer对象,因为它们依赖于Comparator接口,该接口是为对象类型设计的。
  2. .sorted(new Comparator<Integer>(){...}):
    • .sorted(...)方法接收一个Comparator<Integer>作为参数,用于定义排序逻辑。
    • 在这个Comparator中,compare方法被重写以定义排序规则。它首先调用一个假定的cntInt(Integer)方法(该方法在代码段中没有给出,但我们可以假设它接受一个Integer并返回一个整数值,这个值可能表示该整数在另一个集合中出现的次数或某种与整数相关联的计数)。
    • 然后,它比较两个整数的cntInt返回值(cnt1和cnt2)。如果这两个计数相同,则使用Integer.compare(o1, o2)来按整数的自然顺序(即数值大小)进行排序。如果计数不同,则根据计数的大小进行排序。
  3. .mapToInt(Integer::intValue):
    • .mapToInt(Integer::intValue)将Stream<Integer>转换回IntStream。这是因为排序和比较完成后,可能希望将结果转换回基本类型的数组以节省内存或出于其他性能考虑。
    • Integer::intValue是一个方法引用,它引用了Integer对象的intValue()方法,该方法返回Integer对象封装的int值。
  4. .toArray():
    • 最后,.toArray()方法将IntStream转换回int[]数组。这是整个链式调用的结果,现在是一个按照自定义排序逻辑(基于cntInt方法的返回值和整数的自然顺序)排序的整数数组。

栈Stack

栈的初始化:

1
Stack<TreeNode> myStack = new Stack<>();

栈的基本操作:

1
2
3
myStack.push(cur);
cur = myStack.pop();
cur = myStack.peek();//只是查看栈顶元素,不会改变栈的内容

队列Queue

使用LinkedList实现Queue

队列的初始化:

1
Queue<TreeNode> myQueue = new LinkedList<>();

队列的基本操作(因为上述Queue的初始化是通过LinkedList实现的,所以此队列的操作函数与LinkedList一致):

1
2
3
4
5
6
myQueue.add(root);
TreeNode cur = myQueue.removeLast();//删除队尾
TreeNode cur = myQueue.poll();//删除队头
TreeNode cur = myQueue.peek();
int size = myQueue.size();//获得大小
while(!myQueue.isEmpty())//判断是否为空

堆PriorityQueue

PriorityQueue 默认是小根堆,大顶堆定义:(基本操作和队列一致)

1
Queue<Integer> bigHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);

链表List

链表的实现分为ArrayList和LinkedList

ArrayList

LinkedList

LinkedList链表的初始化:

1
2
List<Integer> me = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();//嵌套的list,右侧也只需要写一个括号就可以

LinkedList链表的基本操作:

1
2
3
4
result.add(me);
Collections.reverse(result);//翻转list
for (Node child : cur.children)//遍历list
int size = list.size()//获得list的大小

关于链表删除:list.remove()的问题:

1
2
3
4
5
ArrayList<Integer> randomNumbers = new ArrayList<>();
// 删除第一次出现13的元素
randomNumbers.remove(Integer.valueOf(13));
// 删除13位置的元素,而不是元素13,因为元素13的类型是Integer
randomNumbers.remove(13);

字符串

String

char[]转String:

1
String str = new String(charArray); 

字符串长度:

1
int len = str.length();

关于数组Array的长度获取是arr.length,但是字符串String的长度获取是str.length()的原因:

  • 数组在Java中是一种基础数据类型,但也被视为对象(因为它们有引用类型的特性)。数组一旦被创建,其长度就是固定的,并且这个长度信息是作为数组对象的一部分直接存储的。因此,当你访问array.length时,实际上是在直接访问这个数组对象的内置属性(或者说元数据),而不是在调用一个方法。这就是为什么不需要括号的原因——因为这不是一个函数调用,而是一个直接访问操作。

  • 字符串(String)在Java中是一个类(java.lang.String),而不是基础数据类型。这意味着字符串是一个对象,拥有属性和方法。String类的length()方法是一个实例方法,用于返回字符串的长度。由于这是一个方法调用,所以需要使用括号来包围参数(尽管length()方法不接受任何参数,但括号是必须的,以区分于属性访问)。

获取字符串某个位置的元素:

1
char c = str.charAt(0);

遍历字符串:使用for+str.charAt()

String转char[]:

1
char[] array = str.toCharArray();

注:String对象一旦创建,实体是不可以变化的,即内容不能再修改

判断相等:

==判断的是否为同一对象,str.equals(“abc”)判断的是内容

子串切割:注意切割之后的子串是[i,j)

1
str = str.substring(i,j);

StringBuilder

字符串String倒置:

1
return new StringBuffer(str).reverse().toString();

集合Set(Collections)

集合的初始化:

1
Set<String> set = new HashSet<String>();

集合的遍历:

1
2
3
4
5
6
7
8
9
通过foreach
for(String s:set){
System.out.println(s);
}
遍历,通过迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}

集合添加元素:

1
set.add(“one”);

集合删除元素:

1
set.remove(“one”);

集合是否包含元素:

1
2
3
if(set.contains(“one”)){
;
}

集合转数组:

需要注意,new String[0]新建了一个String数组,但是它的长度是0。其作用是,1.告诉toArray方法你期望的数组类型是什么,这对于泛型集合尤为重要,因为泛型信息在运行时会被擦除,所以toArray方法需要某种方式来知道它应该创建什么类型的数组。2.触发新数组分配:由于数组的长度为0,显然不足以存储集合中的任何元素(除非集合本身就是空的)。因此,toArray方法会意识到需要分配一个新的数组来存储所有元素。

当然,如果你知道集合的大小(或至少有一个合理的估计),传入一个足够大的数组可能会更有效率。如果传入的数组足够大以容纳集合中的所有元素,toArray方法就会直接在这个数组中填充元素,而不是分配一个新的数组。然而,这要求你能够提前知道或估计集合的大小,这在很多情况下是不可行的。

1
String[] strings = hashSet.toArray(new String[0]); 

另外:

1
2
Set<Integer> result = new HashSet<>(); 
Integer[] integers = result.toArray(new Integer[0]);

这种方法只能直接转成Integer[ ],而不能直接转成int[ ],可以使用下面的思路:

1
2
3
return result.stream()  
.mapToInt(Integer::intValue) //将Stream<Integer>转换为IntStream
.toArray(); //使用toArray()无参版本会返回int[]数组

映射Map(Collections)

HashMap

初始化:

1
Map<String, Integer> map = new HashMap<>();

增加元素:

1
map.put("one", 1);

查找元素:

1
System.out.println(map.get("one"));

移除元素:

1
map.remove("three");

是否包含元素:

1
map.containsKey("one");

如果找不到元素,返回默认值:

1
map.getOrDefault(1, "Not Found");

Map的遍历:

1
for (int num : me.keySet())

3.做题笔记

数组

704.二分查找

二分的特征:升序+无重复

大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

定义target在[left, right]区间:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

防止溢出:

1
int middle = left + ((right - left) / 2); 防止溢出 等同于(left + right)/2

27.移除元素

区间的定义是[left,right]:

  • while(left<=right)
  • left++;right–;

因为while的过程当中可能会超出限制,所以要while (left <= nums.length - 1 && nums[left] != val),而且要if (left <= right)再交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int removeElement(int[] nums, int val) {
// 左右快慢指针,区间是[l,r]
int left = 0;
int right = nums.length - 1;
while (left <= right) {
while (left<=nums.length-1&&nums[left] != val) {
left++;
}
while (right>=0&&nums[right] == val) {
right--;
}
if (left <= right ) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
return left;
}
}

977. 有序数组的平方

双指针,绝对值最大的数字一定是在两边,结果数组的最后一位由双指针确定,结果数组从后往前依次确定

209.长度最小的子数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int minSubArrayLen(int target, int[] nums) {
// 双指针构成滑动窗口,区间是[l,r]
int left = 0;
int sum = 0;
int result = nums.length + 1;
for (int right = left; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {// 注意这里
result = Math.min(result, right - left + 1);
sum = sum - nums[left];
left++;
}
}
return result == nums.length + 1 ? 0 : result;
}
}

59.螺旋矩阵II

需要注意:

  • 确定好每一次的填充都是左闭右开的填充
  • 奇数的矩阵要比偶数的矩阵再多一个填充中间位置的元素的步骤

1365.有多少小于当前数字的数字

排序,map记录( nums[i] , 几个比它小的数 )

最后按照原来数组的顺序依次在map当中寻找结果

941.有效的山脉数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public boolean validMountainArray(int[] arr) {
// 1.根据isTop来判断是否已经达到顶峰
int len = arr.length;
if (len < 3) {
return false;
}
if (arr[1] < arr[0]) {
return false;
}
// true之后所有的元素都要严格下降
boolean isTop = false;
for (int i = 1; i < len; i++) {
if (arr[i] == arr[i - 1]) {
return false;
}
if (arr[i] < arr[i - 1] && isTop == false) {
isTop = true;
}
if (isTop && arr[i] >= arr[i - 1]) {
return false;
}
}
return isTop;
// 2.双指针
int len = arr.length;
int left = 0;
int right = len - 1;
while (left + 1 < len && arr[left] < arr[left + 1]) {
left++;
}
while (right - 1 >= 0 && arr[right] < arr[right - 1]) {
right--;
}
return left == right && left != 0 && right != len - 1;
}
}

1207.独一无二的出现次数

使用map记录每一个元素的出现次数,最后依次把出现次数放到set当中,如果set已经存在这个次数了就返回false

283. 移动零

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public void moveZeroes(int[] nums) {
// 双指针法,fast遍历数组,如果fast位置不是0,那就直接赋值给slow,最后slow没走完的位置都是0
int slow = 0;
int fast = 0;
for (; fast < nums.length; fast++) {
if (nums[fast] != 0) {
nums[slow] = nums[fast];
slow++;
}
}
for (int i = slow; i < nums.length; i++) {
nums[i] = 0;
}
}
}

189. 旋转数组

1.倒序前len-k个元素,倒序后k个元素,倒序整个数组

2.双指针:右轮转k次,每一次轮转都有:记录nums[0], slow=0, fast=slow+1, nums[slow]=nums[fast]

3.暴力解法,O(n)的空间,先把后k个元素复制到新的数组当中,再把剩下的复制

724.寻找数组的中心下标

遍历数组求出总和,第二次遍历再计算leftSum和rightSum是否相等。

其中,leftSum的定义是从0到i为止(包括i的sum),rightSum也是包括i的sum。这样可以少很多特殊条件的判断

922. 按奇偶排序数组II

双指针,slow只关心偶数位置,fast只关心奇数位置

75. 颜色分类

分别记录0,1,2有多少个即可

26. 删除有序数组中的重复项

快慢指针

448. 找到所有数组中消失的数字

可以考虑原地修改数组(这种方法在力扣一些题当中会用到,但是不提倡,因为会修改原有数组,工作当中不常用):

把[0,n-1]位置作为[1,n]数字的映射,数组当中出现数字x,就把nums[x-1]的位置赋为负数

35. 搜索插入位置

二分查找即可

287. 寻找重复数

把[0,n-1]的位置作为数字[1,n]的映射,遇到数字x,就把x-1位置的元素赋为负数

73. 矩阵置零

记录需要赋值为0的行和列即可

74. 搜索二维矩阵

定位到哪一行再二分查找即可

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

二分查找,再向左向右探索即可

3. 无重复字符的最长子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
// 双指针[left,right],判断每一个left取值时,最长的不重复子串长度
public int lengthOfLongestSubstring(String s) {
int len = s.length();
if (len == 0) {
return 0;
}
int result = 1;
int left = 0;
int right = -1;
Set<Character> set = new HashSet<>();
while (left < len) {
if (left - 1 >= 0) {
set.remove(s.charAt(left - 1));
}
left++;
while (right + 1 < len && !set.contains(s.charAt(right + 1))) {
set.add(s.charAt(right + 1));
right = right + 1;
}
result = Math.max(result, set.size());
}
return result;
}
}

153. 寻找旋转排序数组中的最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public int findMin(int[] nums) {
int len = nums.length;
int left = 0;
int right = len - 1;
// 思路1
// while (left < right) {
// int mid = (right - left) / 2 + left;
// // mid在前一半当中
// if (nums[mid] > nums[len - 1]) {
// left = mid + 1;
// } else {
// right = mid;
// }
// }
// 思路2
if (nums[0] <= nums[len - 1]) {
return nums[0];
}
while (left < right) {
int mid = (right - left) / 2 + left;
// mid在前一半当中
if (nums[mid] >= nums[0]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
}

33. 搜索旋转排序数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
// 先二分找到最小值,再分段查找即可
public int search(int[] nums, int target) {
int minIndex = findMIn(nums);
// 递增序列
if (minIndex == 0) {
return bSearch(nums, target, 0, nums.length - 1);
}
if (nums[0] == target) {
return 0;
} else if (nums[0] < target) {
return bSearch(nums, target, 0, minIndex - 1);
} else {
return bSearch(nums, target, minIndex, nums.length - 1);
}
}

public int findMIn(int[] nums) {
int len = nums.length;
// 递增序列
if (nums[0] < nums[len - 1]) {
return 0;
}
int left = 0;
int right = len - 1;
while (left < right) {
int mid = (right - left) / 2 + left;
if (nums[mid] >= nums[0]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}

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

54. 螺旋矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
List<Integer> result = new ArrayList<>();
// 每一次都留下最后一个位置不处理
for (int k = 0; k < m / 2 && k < n / 2; k++) {
for (int i = k; i < n - k - 1; i++) {
result.add(matrix[k][i]);
}
for (int i = k; i < m - k - 1; i++) {
result.add(matrix[i][n - k - 1]);
}
if (m - k - 1 > k) {
for (int i = n - k - 1; i > k; i--) {
result.add(matrix[m - k - 1][i]);
}
}
if (n - k - 1 > k) {
for (int i = m - k - 1; i > k; i--) {
result.add(matrix[i][k]);
}
}
}
// 最后还要加元素的处理
if (m % 2 == 0 && n % 2 == 0) {
return result;
}
if (m == n) {
if (m % 2 == 1) {
result.add(matrix[m / 2][n / 2]);
}
} else if (m / 2 < n / 2) {
for (int i = m / 2; i <= m / 2 + (n - m); i++) {
result.add(matrix[m / 2][i]);
}
} else if (m / 2 > n / 2) {
for (int i = n / 2; i <= n / 2 + (m - n); i++) {
result.add(matrix[i][n / 2]);
}
}
return result;
}
}

48. 旋转图像

[i,j]->[j,n-1-i]->[n-1-i,n-1-j]->[n-1-j,i],注意n为奇偶数时需要处理的i和j的范围

240. 搜索二维矩阵 II

从右上角开始查找,如果target>matrix[i] [j],那么就往下,反之则往左

31. 下一个排列

举例:2 4 5 3(i) 4 4(j) 2 1->2 4 5 4 1 2 3 4

考察最右侧的递减序列:从右到左查找到3(i),把比3大的最小值4(j)交换:2 4 5 4(i) 4 3(j) 2 1,之后把4 3(j) 2 1进行排序

由于原本4 4(j) 2 1是递减序列,并且交换的3是比4更小的值,因此交换之后的序列仍然是递减序列,这里排序只需倒转即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public void nextPermutation(int[] nums) {
int len = nums.length;
int i = len - 1;
while (i - 1 >= 0 && nums[i - 1] >= nums[i]) {
i--;
}
i--;
if (i >= 0) {
int j = len - 1;
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
}
reverse(nums, i + 1, len - 1);
}

public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}

// 逆转nums[i,j]
public void reverse(int[] nums, int i, int j) {
for (int k = i; k <= (i + j) / 2; k++) {
swap(nums, k, i + j - k);
}
}
}

643. 子数组最大平均数 I

即寻找一个长度为k且加和sum最大的连续子数组

1431. 拥有最多糖果的孩子

先遍历一遍candies,确定如果要成为最多糖果的孩子,至少需要多少糖果

605. 种花问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int times = 0;
int len = flowerbed.length;
for (int i = 0; i < len; i++) {
if (flowerbed[Math.max(i - 1, 0)] == 0 && flowerbed[i] == 0
&& flowerbed[Math.min(i + 1, len - 1)] == 0) {
flowerbed[i] = 1;
times++;
}
}
return times >= n;
}
}

2215. 找出两数组的不同

使用HashSet即可

链表

203.移除链表元素

这类问题最重要的是在草稿上画出节点变动的过程,否则写起来会很麻烦。以及需要注意,while的条件到底是 cur!=null 还是 cur.next!=null ,以及遇到 cur.next.next 的时候要注意判断 cur.next==null。

这道题比较简单,需要注意删除cur节点的时候,pre和cur的处理。以及要新建结果链表的新的头节点result,因为结果链表的头节点有可能不是head

206.反转链表

这道题比较简单,头插法:cur摘掉(cur一直在head后面),插在result和result.next中间

24. 两两交换链表中的节点

这道题需要对每两个一组的cur1,cur2以及pre的操作在草稿上面画的清楚。并且需要注意,一开始pre的定义是pre.next=head,那么要把这个时候pre的位置记下来,返回这个位置的next才是最后的头节点

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

快慢指针,快指针比慢指针先走N-1个位置,fast走到最后的时候,slow就是倒数N个节点,再记录slow前一个节点pre,通过pre吧slow删除

160. 相交链表

快慢指针,在短链表里面的慢指针每次走一步,从头节点开始走。在长链表里面的快指针每次走一步,但是从第abs(m-n)个节点开始走

142.环形链表II

快慢指针,快指针走两步,慢指针走一步。当快慢相遇的时候,再派一个慢指针slow2,slow2和slow1相遇的位置就是入口

2. 两数相加

把当前进位带到下一位即可,别忘了最后一个进位有可能新建节点,tail指向最后一个确定val的节点

148. 排序链表

把所有val放到数组当中排列再新建链表

23. 合并 K 个升序链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode head = null;
ListNode tail = head;
while (isVaild(lists)) {
int val = 100001;
int j = -1;
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null && val > lists[i].val) {
j = i;
val = lists[j].val;
}
}
if (j != -1) {
if (head == null) {
head = new ListNode(val);
tail = head;
} else {
tail = add(tail, val);
}
lists[j] = lists[j].next;
}
}
return head;
}

public ListNode add(ListNode tail, int val) {
ListNode node = new ListNode(val, null);
tail.next = node;
tail = tail.next;
return tail;
}

public boolean isVaild(ListNode[] lists) {
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null) {
return true;
}
}
return false;
}
}

138. 随机链表的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Node copyRandomList(Node head) {
// 先根据每一个旧节点的val创建一个新节点的半成品
// 并且使用使用哈希表存放这个半成品
// 之后再从哈希表当中取出对应的半成品赋值random和next
Map<Node, Node> map = new HashMap<>();
Node curHead = head;
while (curHead != null) {
map.put(curHead, new Node(curHead.val));
curHead = curHead.next;
}
curHead = head;
while (curHead != null) {
map.get(curHead).next = map.get(curHead.next);
map.get(curHead).random = map.get(curHead.random);
curHead = curHead.next;
}
return map.get(head);
}

哈希表

242.有效的字母异位词

什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。

使用数组存储26个字母的出现频率,构造简易的哈希表

349. 两个数组的交集

输出结果中的每个元素一定是唯一的,所以考虑HashSet当作哈希表

202.快乐数

不符合要求的n会无限循环,也就是结果会重复出现。因此需要使用set来记录每一次的结果,如果重复出现了就不是快乐数

1.两数之和

1.排序+快慢指针

2.数组中同一个元素在答案里不能重复出现,想到使用HashMap。这里需要注意,为什么不是HashSet呢,因为我们最后需要返回元素的下标,那么哈希表就需要存放元素和下标位置,使用HashMap可以轻松解决。

并且需要注意的是,我们需要在哈希表当中寻找元素是否出现过,那么key得是元素。

383. 赎金信

使用数组存储26个字母的出现频率,构造简易的哈希表即可

15.三数之和

先排序,固定slow1,对接下来的数组使用快慢指针来寻找target==0-nums[slow1]的两个元素

需要注意的是,答案返回组成三元组的元素数值,并且当中不可以有重复的三元组,那么就需要去重:

1
2
3
4
5
6
7
8
9
10
11
// 如果slow1和slow1-1一样,那么slow1-1的位置可以确保一定被考虑过,所以要判断的是slow1-1和slow1
while (slow1 - 1 >= 0 && slow1 < nums.length - 1 && nums[slow1] == nums[slow1 - 1]){
slow1++;
}
// 此时slow2和fast已经处理过了,要看接下来的slow2+1和fast-1是否还和slow2/fast一样
while (slow2 + 1 < fast && nums[slow2] == nums[slow2 + 1]) {
slow2++;
}
while (slow2 < fast - 1 && nums[fast] == nums[fast - 1]) {
fast--;
}

注:Set无法直接去重,因为list1:{1,2,3}和list2:{2,1,3}不会被认为是重复的

18.四数之和

对于去重的操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
while (i - 1 >= 0 && i + 1 <= nums.length - 1 && nums[i] == nums[i - 1]) {
i++;
}
while (j - 1 >= i + 1 && j + 1 <= nums.length - 1 && nums[j] == nums[j - 1]) {
j++;
}
// 此时slow和fast已经处理过了,要看接下来的slow+1和fast-1是否还和slow/fast一样
while (slow + 1 < fast && nums[slow] == nums[slow + 1]) {
slow++;
}
while (slow < fast - 1 && nums[fast] == nums[fast - 1]) {
fast--;
}

另外,这道题可能会涉及到int溢出的问题,所以要强转成long:

1
2
long myTarget = target - (long) nums[i] - (long) nums[j];
if ((long) nums[slow] + nums[fast] == myTarget)

454.四数相加II

三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,有很多细节需要处理。这道题和三数相加、四数相加的思路都不一样,是使用哈希法的经典题目,应该使用HashMap。

思路是:统计两个数组AB中的元素之和,同时统计出现的次数,放入map。再统计剩余的两个元素CD的和,在map中找是否存在相加为0的情况,同时记录次数。

146. LRU 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class LRUCache {
int capacity = 0;
Node head = new Node();// 伪头部
Node tail = new Node();// 伪尾部
// head<=>1<=>2<=>tail
Map<Integer, Node> LRU = new HashMap<>();

public LRUCache(int capacity) {
this.capacity = capacity;
head.next = tail;
tail.pre = head;
}

public int get(int key) {
if (LRU.containsKey(key)) {
Node node = LRU.get(key);
moveHead(node);
return node.value;
} else {
return -1;
}
}

public void put(int key, int value) {
if (LRU.containsKey(key)) {
Node node = LRU.get(key);
node.value = value;
moveHead(node);
return;
}
if (LRU.size() == capacity) {
LRU.remove(tail.pre.key);//注意这里
delTail();
}
Node node = new Node(key, value, null, null);
addHead(node);
LRU.put(key, node);
}

public void addHead(Node node) {
node.next = head.next;
node.pre = head;
head.next.pre = node;
head.next = node;
}

public void delTail() {
Node newTail = tail.pre.pre;
newTail.next = tail;
tail.pre = newTail;
}

public void moveHead(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
addHead(node);
}

}

class Node {
Node pre;
Node next;
int key;
int value;

public Node(int key, int value, Node pre, Node next) {
this.key = key;
this.pre = pre;
this.next = next;
this.value = value;
}

public Node() {
}
}

438. 找到字符串中所有字母异位词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public List<Integer> findAnagrams(String s, String p) {
// 滑动窗口
Map<Character, Integer> sMap = new HashMap<>();
Map<Character, Integer> pMap = new HashMap<>();
List<Integer> result = new ArrayList<>();
int sLen = s.length();
int pLen = p.length();
if (sLen < pLen) {
return result;
}
for (int i = 0; i < pLen; i++) {
pMap.put(p.charAt(i), pMap.getOrDefault(p.charAt(i), 0) + 1);
sMap.put(s.charAt(i), sMap.getOrDefault(s.charAt(i), 0) + 1);
}
for (int i = 0; i + pLen - 1 < s.length(); i++) {
if (compare(sMap, pMap)) {
result.add(i);
}
if (i + pLen < s.length()) {
char c = s.charAt(i);
if (sMap.get(c) == 1) {
sMap.remove(c);
} else {
sMap.put(c, sMap.get(c) - 1);
}
sMap.put(s.charAt(i + pLen), sMap.getOrDefault(s.charAt(i + pLen), 0) + 1);
}
}
return result;
}

public boolean compare(Map<Character, Integer> sMap, Map<Character, Integer> pMap) {
Map<Character, Integer> map = new HashMap<>();
map.putAll(sMap);
for (char c : pMap.keySet()) {
map.remove(c, pMap.get(c));
}
return map.size() == 0;
}
}

字符串

344.反转字符串

使用左右指针即可

541. 反转字符串II

这道题需要稍微注意以下两个条件的判断即可:

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

以及String对象一旦创建,实体是不可以变化的,即内容不能再修改。所以应该将String转成char[]再操作

165. 比较版本号

一个个比较即可

13. 罗马数字转整数

依次遍历,只需处理每一位是加还是减的逻辑即可

345. 反转字符串中的元音字母

使用双指针left和right指向元音的位置,反转left和right位置即可

动态规划

相关知识

动态规划中每一个状态一定是由上一个状态推导出来的,而贪心是局部直接选最优的。

动态规划方法论:

  • 确定dp[i]数组的含义
  • 确定递推公式
  • dp数组如何初始化
  • 确定遍历顺序
  • 举例推导dp数组

写动规题目,代码出问题很正常!找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!

01背包:

1.dp[i] [j]:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

2.dp[i] [j] = max ( dp[i - 1] [j]不加入物品i , dp[i - 1] [j - weight[i]] + value[i] 加入了物品i );

3.dp[0] [j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0] [j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0] [j] 应该是value[0],因为背包容量放足够放编号0物品。

如果背包容量j为0的话,即dp[i] [0],无论是选取哪些物品,背包价值总和一定为0。

4.从推导公式可以看出,每一次dp[i] [j]的确定都需要dp[i - 1] [j]和dp[i - 1] [j - weight[i]]的确定,都在左上角和正上方的方向,所以先i(物品)后j(容量),或者先j后i都可以,推荐使用先i物品后j容量,更加符合我们平时的习惯,而且一定不会错。

进阶:把二维压缩成一维数组:

在使用二维数组的时候,递推公式:

1
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i] [j] = max(dp[i] [j], dp[i] [j - weight[i]] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

1.dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

2.那么递推公式为:

1
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

3.dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。

那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢?

看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。

这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。

那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。

4.遍历顺序:

1
2
3
4
5
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}

倒序遍历是为了保证物品i只被放入一次!但如果一旦正序遍历了,那么物品0就会被重复加入多次!

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

为什么倒序遍历,就可以保证物品只放入一次呢?

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?

不可以!

因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

完全背包

而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

1
2
3
4
5
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}

其中i和j的嵌套顺序是可以改变的

注意!任何一个问题当中,有限的资源/要达到的限制条件是背包的容量,最大化的目标是背包的价值

完全背包的进阶:排列组合问题

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

具体说明:

我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

代码如下:

1
2
3
4
5
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}

假设:coins[0] = 1,coins[1] = 5。

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!

如果把两个for交换顺序,代码如下:

1
2
3
4
5
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

此时dp[j]里算出来的就是排列数!

509. 斐波那契数

1.dp[i]=斐波那契i位置的数值

2.dp[i]=dp[i-1]+dp[i-2]

3.dp[0]=0,dp[1]=1

4.从左到右遍历

70. 爬楼梯

1.dp[i]爬到i阶梯有多少方法爬到

2.dp[i]=dp[i-1]+dp[i-2]

3.dp[0]=1 dp[1]=1

4.从左到右遍历

746. 使用最小花费爬楼梯

1.dp[i]是爬到楼梯i的最小花费

2.dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

3.dp[0]=0 dp[1]=0

4.从左到右遍历

62.不同路径

1.dp[i] [j]到达(i,j)位置有多少路径

2.dp[i] [j]=dp[i-1] [j]+dp[i] [j-1]

3.dp[0] [j]=1 dp[i] [0]=1

4.从左到右,从上到下

63. 不同路径 II

1.dp[i] [j]为到达i,j位置的路径总数

2.dp[i] [j]=dp[i-1] [j]+dp[i] [j-1] dp[障碍物]=0

3.dp[0] [j]=1(如果这一列都没有障碍物,否则在障碍物之后都是0) dp[i] [0]=1(同理)

4.从左向右,从上向下

343. 整数拆分

1.dp[i]表示拆分数字i可以获得的最大乘积

2.dp[i]=max(dp[i-1]×1 , dp[i-2]×2 , , , , dp[1]×(i-1) , (i-1)×1 , (i-2)×2 , , , 1×i)

这里需要注意:==j * (i - j) 是单纯的把整数拆分为两个数相乘==,而 j * dp[i - j]是拆分成两个以及两个以上的个数相乘

3.dp[1]=1

4.i从左到右

96.不同的二叉搜索树

1.dp[i]是i个节点构成的二叉树的个数

2.把任何一个由i个节点构成的二叉树都看成是左+右子树,左子树可以有的节点是0到i-1个,右子树也同理,该树的构成种类是左子树构成种类*右子树构成种类。dp[i]=sum( dp[i-1]×dp[0] , dp[i-2]×dp[1] , , , , )

3.dp[0]=1

4.i从小到大

416. 分割等和子集

01背包问题,背包的容量是sum/2。每个数字的价值是num[j],每个数字占有容量nums[j],最后查看sum/2==nums[sum/2]

1.dp[j]是背包容量为j的背包,可以获得的最多的价值

2.dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])

3.dp[j]都是0

4.i从小到大,j从大到小

1049.最后一块石头的重量II

转化成sum/2的01背包问题,最后返回sum - 2 * dp[dp.length - 1]

1.dp[i]表示容量为i的背包装下的最大值

2.递推公式是dp[i]=max(dp[i],dp[i-weight]+weight)

3.初始化都是0

4.递推顺序是stone正向,背包反向

279.完全平方数

1.dp[i]表示组成数字i的完全平方数的最小的数量

2.dp[i]=min(dp[i-1],dp[i-4],,,,)+1

3.dp[小于n的完全平方数]=1

4.i从小到大

121. 买卖股票的最佳时机

1.dp[i] [0]表示第i天是不持有股票的状态,所具有的资金

dp[i] [1]表示的是持有股票的状态所具有的资金,初始资金是0

2.dp[i] [0]=max(dp[i-1] [1]+prices[i],dp[i-1] [0])

dp[i] [1]=max(-prices[i],dp[i-1] [1]),题里面只要求买卖一次,所以无论何时购买,资金都是-prices[i]

3.dp[0] [0]=0 dp[0] [1]=-prices[0]

4.i从左到右

122.买卖股票的最佳时机II

1.dp[i] [0]表示第i天不持有股票所持有的最大资金

dp[i] [1]表示第i天持有股票所持有的最大资金

2.dp[i] [0]=max(dp[i-1] [0] , dp[i-1] [1]+prices[i])

dp[i] [1]=max(dp[i-1] [1],dp[i-1] [0]-prices[i])

3.dp[0] [0]=0 dp[0] [1]=-prices[0]

4.i从小到大

123.买卖股票的最佳时机III

1.dp[i] [0]第i天不持有股票,1第一次持有股票,2第一次不持有股票,3第二次持有股票,4第二次不持有股票

2.dp[i] [0]=dp[i-1] [0]

dp[i] [1]=max(dp[i-1] [1],dp[i-1] [0]-prices[i])

dp[i] [2]=max(dp[i-1] [2],dp[i-1] [1]+prices[i])

dp[i] [3]=max(dp[i-1] [3],dp[i-1] [2]-prices[i])

dp[i] [4]=max(dp[i-1] [4],dp[i-1] [3]+prices[i])

3.dp[0] [0]=0 dp[0] [1]=-prices[0] dp[0] [2]=0 dp[0] [3]=-prices[0] dp[0] [4]=0

4.i从小到大

188.买卖股票的最佳时机IV

1.dp[i] [0]是第i天不持有股票,1是第一次持有,2是第一次不持有,,,

2.dp[i] [0]=dp[i-1] [0]

dp[i] [2×k+1]=max(dp[i] [2×k]-prices[i],dp[i-1] [2×k+1])

dp[i] [2×k+2]=max(dp[i] [2×k+1]+prices[i],dp[i-1] [2×k+2])

3.dp[0] [2×k+1]=-prices[0]

dp[0] [2×k]=0

4.i从小到大

714.买卖股票的最佳时机含手续费

1.dp[i] [0]在第i天不持有股票所拥有的资金,1持有

2.dp[i] [0]=max(dp[i-1] [0],dp[i-1] [1]+prices[i]-fee)

dp[i] [1]=max(dp[i-1] [1],dp[i-1] [0]-prices[i])

3.dp[0] [0]=0

dp[0] [1]=-prices[0]

4.i从小到大

647. 回文子串

1.dp[i] [j]表示s当中[i,j]是否是回文串

2.dp[i] [j]= 0(false);

1(true)当且仅当s[i]=s[j]&&dp[i+1] [j-1]=true,或j=i+1的时候s[i]==s[j]也可以(“aa”)

3.dp[i] [i]=1

4.根据递推公式可以得出,dp[i] [j]根据左下角的值dp[i+1] [j-1],j从左往右,i从下往上

198.打家劫舍

1.dp[i]表示截止到第i家位置所获得的最大金额

2.dp[i]=max(dp[i-2]+nums[i],dp[i-1])

3.dp[0]=nums[0] dp[1]=max(nums[0],nums[1])

4.i从小到大

213.打家劫舍II

1.dp[i]表示截止到第i家位置所获得的最大金额

2.dp[i]=max(dp[i-2]+nums[i],dp[i-1])

3.dp[0]=nums[0] dp[1]=max(nums[0],nums[1])

4.==i考虑首元素不考虑尾元素来一遍打劫,考虑尾元素不考虑首元素来一遍打劫==

337.打家劫舍 III

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int rob(TreeNode root) {
// 后序遍历,每一个节点的遍历都存储维护一个dp数组,是树形的动态规划
// dp[0]表示不抢当前节点,dp[1]表示抢当前节点
// dp[0]=max(left[0],left[1])+max(right[0],right[1])
// dp[1]=cur.val+left[0]+right[0]
// cur==null,return [0,0]
// 后序遍历
int[] dp = robNode(root);
return Math.max(dp[0], dp[1]);
}

public int[] robNode(TreeNode cur) {
if (cur == null) {
return new int[] { 0, 0 };
}
int[] left = robNode(cur.left);
int[] right = robNode(cur.right);
int[] dp = new int[2];
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = cur.val + left[0] + right[0];
return dp;
}
}

376. 摆动序列

1.dp[i]表示截止到i位置,当前的摆动序列的最长子序列的长度,[0]表示i作为低位置,[1]表示i作为高位置

2.dp[i] [0]=(nums[i]<nums[i-1])dp[i-1][1]+1 else dp[i-1][0]

dp[i] [1]=(nums[i]>nums[i-1])dp[i-1][0]+1 else dp[i-1][1]

3.dp[0] [0]=1 dp[0] [1]=1

4.i从小到大

53. 最大子序和

1.dp[i]表示截止到i位置(包括i位置),连续子数组的最大和

2.dp[i]=max(dp[i-1]+nums[i],nums[i])

3.dp[0]=nums[0]

4.i从小到大

注意根据dp[i]的定义,最终结果应该是dp当中的max,而不是dp[len-1]

300.最长递增子序列

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

为什么一定表示 “以nums[i]结尾”的最长递增子序,因为我们在做递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么如何可以算作递增呢。

1.dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

2.dp[i]=(for j<i if(num[j]<num[i]){dp[i]=max(dp[j])+1;})

3.dp[i]=1

4.i从小到大

674. 最长连续递增序列

1.dp[i]表示截至到i位置最长的连续递增子序列(包括i位置)

2.dp[i]=(nums[i]>nums[i-1] dp[i-1]+1) else 1

3.dp[0]=1

4.i从小到大

1143.最长公共子序列

1.dp[i] [j]表示text1的i为止和text2的j为止,最长的公共子序列长度

2.dp[i] [j]=dp[i-1] [j-1]+1(text1[i]==text2[j])/max(dp[i-1] [j],dp[i] [j-1])

3.dp[0] [j]=0,等到text1[0]==text2[j]之后都是1,dp[i] [0]也是同理

4.i从小到大,j从小到大

1035.不相交的线

线的个数就是最长公共子序列的长度,这道题同1143题

718. 最长重复子数组

1.dp[i] [j]表示nums1到i为止,nums2到j为止,最长的公共子数组长度(子数组都是连续的)

2.dp[i] [j]=dp[i-1] [j-1]+1(nums1[i]==nums2[j]),dp[i][j]只能通过这种方式累计

3.dp[0] [j]只有nums1[0]==nums2[j]才是1

4.i从小到大,j从小到大

494.目标和

target=x-(sum-x),x=(target+sum)/2,x是背包的容量

把问题转化成容量为x的背包,装满这个背包的方法有多少

1.dp[j]为把容量为i的背包装满的方式个数

2.dp[j]=+dp[j-nums[i]]

3.dp[0]=1

4.i正向,j逆向

474.一和零

1.dp[i] [j]表示对0是容量m,对1是容量n的背包可以最多装多少字符串

2.dp[i] [j]=max(1+dp[i-char0[k]] [j-char1[k]],dp[i] [j])

3.dp[0] [0]=0

4.i,j都倒序,k遍历字符串在最外面正序

518.零钱兑换II

完全背包问题

1.dp[j]表示容量为j的背包装满的方式有多少

2.dp[j]+=dp[j-coins[i]]

3.dp[0]=1

4.i遍历硬币正向,j遍历背包容量正向

377. 组合总和 Ⅳ

完全背包

1.dp[j]表示容量为j的背包可以装满的方式个数

2.dp[j]+=dp[j-nums[i]]

3.dp[0]=1

4.i正向遍历数字在里面,j正向遍历背包在外面

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

322. 零钱兑换

完全背包问题

1.dp[j]表示装满容量为j的背包的所需要的最小的硬币个数

2.dp[j]=min(dp[j],dp[j-coins[i]]+1),dp[0]=0

3.dp[j]都初始化成amount+1

4.i正向外面,j正向里面

309.最佳买卖股票时机含冷冻期

1.dp[i]表示第i天:0不持有股票且不在冷静期,1不持有股票且在冷静期,2持有股票

2.dp[i] [0]=max(dp[i-1] [0],dp[i-1] [1])

dp[i] [1]=dp[i-1] [2]+prices[i]

dp[i] [2]=max(dp[i-1] [0]-prices[i],dp[i-1] [2])

3.dp[0] [0]=0,dp[0] [1]=0,dp[0] [2]=-prices[0]

4.i从1开始

392.判断子序列

即判断s和t的最长公共子序列的长度是否为s的长度即可

1.dp[i] [j]表示s考虑到i为止,t考虑到j位置的最长公共子序列的长度

2.dp[i] [j]=dp[i-1] [j-1]+1(s.charAt(i)==t.charAt(j)) / Math.max(dp[i] [j-1],dp[i-1] [j])

3.dp[0] [j]=1(s.charAt(0)==t.charAt(j)及以后),dp[i] [0]同理

4.i正向,j正向

115.不同的子序列

s是t的子序列 <=> t删除一些字符之后就是s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public int numDistinct(String s, String t) {
// dp[i][j]表示s考虑到i为止,t考虑到j为止,s当中t出现的个数
// if(s.charAt(i)==t.charAt(j)) dp[i][j]=dp[i-1][j]+dp[i-1][j-1]
// 开始解释
// dp[i-1][j]相当于是s考虑到i-1&t考虑到j,dp[i-1][j-1]相当于是s考虑到i-1&t考虑到j-1
// 当s[i]等于t[j]的时候,那么dp[i][j]可以由以上两种情况得到
// (因为已经由s[i]==t[j]了,那么我们可以把t[j]位置删除来匹配)
// 解释完毕
// else dp[i][j]=dp[i-1][j]
// dp[i][0]=累积到目前s.charAt(i)==t.charAt(0)的个数
// dp[0][j]是s只考虑第一个字符的情况下,t是否是s的子序列,所以除了dp[0][0]以外dp[0][j]肯定全是0
// i正向,j正向
int len1 = s.length();
int len2 = t.length();
int[][] dp = new int[len1][len2];
char s0 = s.charAt(0);
char t0 = t.charAt(0);
dp[0][0] = (s0 == t0) ? 1 : 0;
for (int i = 1; i < len1; i++) {
char cur = s.charAt(i);
if (cur == t0) {
dp[i][0] = dp[i - 1][0] + 1;
} else {
dp[i][0] = dp[i - 1][0];
}
}
for (int i = 1; i < len1; i++) {
for (int j = 1; j < len2; j++) {
if (s.charAt(i) == t.charAt(j)) {
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[len1 - 1][len2 - 1];
}
}

583. 两个字符串的删除操作

1.dp[i] [j]表示word1考虑到i位置,word2考虑到j位置,使得word1和word2相同所需的最少删除次数

2.if(word1.charAt(i)==word2.charAt(j)) dp[i] [j]=dp[i-1] [j-1]

else dp[i] [j]=min(dp[i-1] [j]+1(删除word1的i位置),

dp[i] [j-1]+1(删除word2的j位置),

dp[i-1] [j-1]+2(删除word1的i位置和word2的j位置,其实这一项是多余的,前两项可以覆盖这个情况))

3.dp[i] [0]是word1考虑到i和word2第一个字符的最小删除操作,dp[0] [j]同理

4.i正向,j正向

72. 编辑距离

1.dp[i] [j]表示word1考虑到i为止,word2考虑到j为止,最少的操作数

2.if(word1.charAt(i)==word2.charAt(j)) dp[i] [j]=dp[i-1] [j-1]

else dp[i] [j]=min

增:不需要额外考虑,对word1的删除,就是对word2的添加,因此删除和添加的操作是等价的

删:删除i位置,dp[i-1] [j]+1;删除j位置,dp[i] [j-1]+1;删除i和j位置都被包括了,可以不考虑了

换:把i位置换成和j一样的(反过来也一样),那么if(word1.charAt(i)==word2.charAt(j))成立,所以dp[i-1] [j-1]+1

3.dp[0] [j]表示word1第一个字符和word2考虑到j为止,最小的操作数

如果0位置和j相等,dp[0] [j]=j(j这个位置不动,其他位置都要删除),

如果不相等,dp[0] [j]=j+1(如果目前为止没有一个位置和0位置相等) or

j(否则,删除其他j个位置,只保留和0位置相等的那个地方)

dp[i] [0]同理

4.i正向,j正向

516.最长回文子序列

1.dp[i] [j]表示i到j的最长回文子序列的长度

2.dp[i] [j]=(if s.charAt(i)==s.charAt(j))dp[i+1] [j-1]+2

else max(dp[i-1] [j],dp[i] [j-1])

3.dp[i] [i]=1

4.i逆向,j正向

42. 接雨水

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int trap(int[] height) {
// 双指针+dp:
// leftDp[i]表示考虑到位置i,左边的最高柱子
// leftDp[i]=max(leftDp[i-1],height[i])
// leftDp[0]=height[0]
// leftDp从左到右
// rightDp同理,表示右边的最高柱子
// size+=min(leftDp[i],rightDp[i])-height[i]
int len = height.length;
int[] leftDp = new int[len];
int[] rightDp = new int[len];
rightDp[len - 1] = height[len - 1];
leftDp[0] = height[0];
for (int i = 1; i < len; i++) {
leftDp[i] = Math.max(leftDp[i - 1], height[i]);
}
for (int i = len - 2; i >= 0; i--) {
rightDp[i] = Math.max(rightDp[i + 1], height[i]);
}
int size = 0;
for (int i = 0; i < len; i++) {
size += Math.min(leftDp[i], rightDp[i]) - height[i];
}
return size;
}
}

64. 最小路径和

1.dp[i] [j]表示走到(i,j)位置的最小路径和

2.dp[i] [j]=min(dp[i-1] [j],dp[i] [j-1])+grid[i] [j]

3.dp[0] [j]=sum(grid[0] [0]到grid[0] [j]),dp[i] [0]同理

4.从上到下,从左到右

45. 跳跃游戏 II

1.dp[i]表示从0跳到i这个位置所需的最少跳跃次数

2.dp[i]=min(dp[j])+1,其中nums[j]>=(i-j)

3.dp[0]=0,dp[i]=Integer.MAX_VALUE

4.i从小到大

560.和为 K 的子数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public int subarraySum(int[] nums, int k) {
// 1.该方法的运行时间太长,不会通过
// dp[i][j]表示从i位置到j位置的连续子数组的总和->dp[j]表示到j位置的连续子数组的总和
// dp[i][j]=dp[i][j-1]+nums[j]->dp[j]=dp[j-1]+nums[j]
// dp[i][i]=nums[i]->dp[i]=nums[i]
// j正向
// int len = nums.length;
// int[] dp = new int[len];
// int result = 0;
// for (int i = 0; i < len; i++) {
// dp[i] = nums[i];
// if (dp[i] == k) {
// result++;
// }
// for (int j = i + 1; j < len; j++) {
// dp[j] = dp[j - 1] + nums[j];
// if (dp[j] == k) {
// result++;
// }
// }
// }
// return result;

// 2.使用哈希表存放每一个从0开始累计的可能的sum及其对应的次数
// 这是因为sum(nums[i,i+1,i+2,,,,j])=sum(nums[0,1,2,,,,,j])-sum(nums[0,1,2,,,,,,i-1])
Map<Integer, Integer> map = new HashMap<>();
int len = nums.length;
int result = 0;
int sum = 0;
map.put(0, 1);
for (int i = 0; i < len; i++) {
sum += nums[i];
result += map.getOrDefault(sum - k, 0);
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return result;
}
}

139. 单词拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 1.dp[i+1]表示s考虑到i位置,是否能被wordDict拼接
// 2.dp[i]=dp[j]&&check(s[j,,,i-1])
// 3.dp[0]=true
// 4.i从小到大,j从0到i-1
int len = s.length();
Set<String> wordSet = new HashSet<>(wordDict);
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i < len + 1; i++) {
for (int j = 0; j <= i - 1; j++) {
if (dp[j] && check(wordSet, s, j, i - 1)) {
dp[i] = true;
break;
}
}
}
return dp[len];
}

// 检查s[i,,,j]是否在wordSet当中
public boolean check(Set<String> wordSet, String s, int i, int j) {
String str = s.substring(i, j + 1);
if (wordSet.contains(str)) {
return true;
} else {
return false;
}
}
}

5. 最长回文子串

1.dp[i] [j]表示i到j位置是否是回文子串

2.dp[i] [j]=dp[i+1] [j-1]&&s[i]==s[j]

3.dp[i] [i]=true dp[i] [i+1]=s[i]==s[i+1]

4.i从大到小,j从小到大

221. 最大正方形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public int maximalSquare(char[][] matrix) {
// 1.dp[i][j]表示以(i,j)为右下角,包含的最大正方形的边长
// 如果dp[i][j]设置成到(i,j)为止的矩阵区域当中xxx,那么无法得知正方形的终点在哪了
// 2.dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
// 左/上/左上三个位置的dp都大于0并且num[i][j]==1
// 否则的话,num[i][j]==1:dp[i][j]=1;num[i][j]==0:dp[i][j]=0
// 3.dp[i][0]=0 or 1
// 4.从小到大
int m = matrix.length;
int n = matrix[0].length;
int r = 0;
int[][] dp = new int[m][n];
dp[0][0] = matrix[0][0] == '0' ? 0 : 1;
r = Math.max(r, dp[0][0]);
for (int i = 1; i < m; i++) {
if (matrix[i][0] == '1') {
dp[i][0] = 1;
}
r = Math.max(r, dp[i][0]);
}
for (int i = 1; i < n; i++) {
if (matrix[0][i] == '1') {
dp[0][i] = 1;
}
r = Math.max(r, dp[0][i]);
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (dp[i][j - 1] > 0 && dp[i - 1][j] > 0 && dp[i - 1][j - 1] > 0 && matrix[i][j] == '1') {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
} else if (matrix[i][j] == '1') {
dp[i][j] = 1;
} else if (matrix[i][j] == '0') {
dp[i][j] = 0;
}
r = Math.max(r, dp[i][j]);
}
}
return r * r;
}
}

1137. 第 N 个泰波那契数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int tribonacci(int n) {
// dp[i]表示第i个斐波那契数
// dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
// dp[0]=0,dp[1]=1,dp[2]=1
// i从小到大
if (n == 0) {
return 0;
} else if (n == 1 || n == 2) {
return 1;
}
int result = 0;
int num0 = 0;
int num1 = 1;
int num2 = 1;
for (int i = 3; i <= n; i++) {
result = num0 + num1 + num2;
num0 = num1;
num1 = num2;
num2 = result;
}
return result;
}

120. 三角形最小路径和

1.dp[i] [j]表示到达第i个链表(第i行)的j下标位置的最小路径和

2.dp[i] [j]=(if j<=i-1)min(dp[i-1] [j],dp[i-1] [j-1])+triangle[i] [j]

else dp[i] [i]=dp[i-1] [i-1]+triangle.get(i).get(i);

3.dp[i] [0]=sum(triangle[0] [0]到triangle[i] [0])

4.i从1到n,j从1到i

931. 下降路径最小和

1.dp[i] [j]表示到达位置(i,j)所需的最小路径和

2.dp[i] [j]=min(dp[i-1] [j-1],dp[i-1] [j],dp[i-1] [j+1])+matrix[i] [j]

dp[i] [0]=min(dp[i-1] [0],dp[i-1] [1])+matrix[i] [0]

dp[i] [n-1]=min(dp[i-1] [n-2],dp[i-1] [n-1])+matrix[i] [n-1]

3.dp[0] [j]=matrix[0] [j]

4.i从1到n,j从0到n-1

673. 最长递增子序列的个数

1.dp0[i]表示到i为止的最长递增子序列的长度

2.dp0[i]=max(dp0[j])+1 (if nums[j]<nums[i])

3.dp0[i]=1

4.i从小到大


1.dp1[i]表示到i为止的最长递增子序列的个数

2.dp1[i]=sum(dp1[j]) (if nums[j]<nums[i]&& dp0[j]==dp0[i]-1) 如果dp1[i]为0,赋值为1

3.dp1[0]=1

4.i从小到大

91. 解码方法

1.dp[i]表示到i为止,编码方法的总数

2.(if s[i]>’0’) dp[i]+=dp[i-1]

(if s[i-1] == ‘1’ or s[i-1] == ‘2’ && s[i]<=’6’ && s[i]>=’0’) dp[i]+=dp[i-2]

else:return 0

3.dp[0]=1

4.i从小到大

1218. 最长定差子序列

1.dp[i]表示到i为止的最长定差子序列的长度

2.dp[i]= (if exists arr[j]==arr[i]-difference) dp[j]+1 else 1

3.dp[0]=1

4.i从小到大

646. 最长数对链

1.dp[i]表示考虑到i为止,最长的数对链的长度

2.dp[i]=max(dp[j])+1,(if pairs[j] [1]<pairs[i] [0])

3.dp[i]=1

4.i从小到大

2466. 统计构造好字符串的方案数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int countGoodStrings(int low, int high, int zero, int one) {
// 1.dp[i]表示长度为i的好字符串的方案数量
// 2.dp[i]=dp[i-one]+dp[i-zero]
// 3.dp[0]=1
// 4.i从小到大
final int MOD = 1_000_000_007;
// 注意取余的操作,不可以写成: dp[i] += dp[i - one] % MOD;
int[] dp = new int[high + 1];
dp[0] = 1;
for (int i = 1; i <= high; i++) {
if (i - one >= 0) {
dp[i] = (dp[i] + dp[i - one]) % MOD;
}
if (i - zero >= 0) {
dp[i] = (dp[i] + dp[i - zero]) % MOD;
}
}
int result = 0;
for (int i = low; i <= high; i++) {
result = (result + dp[i]) % MOD;
}
return result;
}
}

740. 删除并获得点数

构造数组me,me[i]=nums当中出现数字i的次数*数字i

1.dp[i]表示考虑到数字i为止获得的最大点数

2.dp[i]=max(dp[i-2]+me[i],dp[i-1])

3.dp[1]=me[1],dp[2]=max(me[1],me[2])

4.i从小到大

790. 多米诺和托米诺平铺

1.dp[i]表示考虑第i列的多米诺排列情况(其中前i-1列都是排满的)

标号:[0] (上空下空) [1] (上·下空) [2] (上空下·) [3] (上·下·)

2.dp[i] [0]=dp[i-1] [3]

dp[i] [1]=dp[i-1] [0]+dp[i-1] [2]

dp[i] [2]=dp[i-1] [0]+dp[i-1] [1]

dp[i] [3]=dp[i-1] [3]+dp[i-1] [1]+dp[i-1] [2]+dp[i-1] [0]

3.dp[0] [0]=0,dp[0] [1]=0,dp[0] [2]=0,dp[0] [3]=1

4.i从小到大

712. 两个字符串的最小ASCII删除和

1.dp[i] [j]表示s1考虑到i,s2考虑到j的最小删除ASCII的最小值

注意:这道题当中我们认为范围是左闭右开,即dp[0] [0]表示的是s1和s2都是空串(这样是方便dp[0][j]和dp[i][0])

2.dp[i] [j]=(if s1[i]==s2[j]) dp[i-1] [j-1]

dp[i] [j]=else min(dp[i] [j-1]+ascii(s2[j]),dp[i-1] [j]+ascii(s1[i]))

3.dp[0] [0]= 0 因为s1和s2都是空串,一定相等

s1是空串,s2是[0,,j)也就是[0,,j-1],为了让s2变成空串(s1),那么s2之内全都删除

dp[0] [j]=dp[0] [j-1]+s2[j]

dp[i] [0]=dp[i-1] [0]+s1[i]

4.i从小到大,j从小到大

983. 最低票价

1.dp[i]表示考虑到第i天为止,所需的最低消费

注意:定义最后一天付费,即如果购买为期7天的通行证,希望从1天到7天无限通行,那么不在1天付钱,而是在7天付钱

2.dp[i]=(if days包含i)min(dp[i-1]+costs[0],dp[i-7]+costs[1],dp[i-30]+costs[2])

else dp[i]=dp[i-1]

3.dp[0]=0

4.i从小到大

栈与队列

20. 有效的括号

构建一个栈,只有栈顶是左括号,要加入的是右括号的时候,让栈弹出,否则就push进去。最后查看栈是否是空栈

1047. 删除字符串中的所有相邻重复项

构建一个栈,只有栈顶元素和要加入的元素相同的时候,让栈弹出,否则就push进去。也可以使用快慢指针(转成字符数组,用快指针处的元素覆盖慢指针处的怨怒是),或者直接在字符串上进行修改(转成StringBuilder或者StringBuffer,直接删除元素)

150. 逆波兰表达式求值

遇到加减乘除就连续pop两次,计算结果之后再压栈

347.前 K 个高频元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 先使用MAP记录每一个元素的出现频率,再使用大顶堆来存储元素,按照频率排序,poll前k个元素
Map<Integer, Integer> me = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
me.put(nums[i], me.getOrDefault(nums[i], 0) + 1);
}
Queue<int[]> qu = new PriorityQueue<>((o1, o2) -> o2[1] - o1[1]);// 根据int[]位置1,也就是频率建立大顶堆
for (int num : me.keySet()) {
qu.add(new int[] { num, me.get(num) });
}
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = qu.poll()[0];
}
return result;
}
}

394. 字符串解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
// 这道题涉及的字符串拼接有正向拼接,也有逆向拼接
// 所以为了方便起见,对每一个[]处理之后,再入栈,最后再统一拼接成结果
public String decodeString(String s) {
Stack<String> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ']') {
// 找到需要重复的字符串
String str = stack.pop();
// 注意字符串的比较相等是equals
while (!stack.peek().equals("[")) {
str = stack.pop() + str;
}
stack.pop();
// 获得重复次数
int times = Integer.valueOf(stack.pop());
stack.push(str.repeat(times));
} else {
// 遇到形如23这样的数字,直接把“23”入栈,方便后面处理
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
int num = 0;
num = 10 * num + s.charAt(i) - '0';
i++;
while (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
num = 10 * num + s.charAt(i) - '0';
i++;
}
stack.push(num + "");
System.out.print(stack.peek() + ";");
if (s.charAt(i) < '0' || s.charAt(i) > '9') {
stack.push(s.charAt(i) + "");
}
} else {
stack.push(s.charAt(i) + "");
}

}
}
// 栈当中所有元素正序组合就是结果
String result = "";
while (!stack.isEmpty()) {
result = stack.pop() + result;
}
System.out.println(result);
return result;
}
}

贪心算法

455.分发饼干

把最大的饼干给最饿的孩子,如果满足不了最饿的孩子,就给第二饿的孩子

860.柠檬水找零

只需要记录5的零钱个数,和10的零钱个数即可。需要注意,给20找零15元的时候,有两种找零方式:10+5或者5*3

135. 分发糖果

动态规划:

1.dp[i]表示第i个孩子分到多少糖

2.dp[i]=if(ratings[i]>ratings[i-1])dp[i-1]+1 else 1

3.dp[i]=1,dp[0]和dp[1]单独赋值

4.i从小到大来一遍,从大到小来一遍

55. 跳跃游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public boolean canJump(int[] nums) {
// 1.从左到右遍历,把能够到达的位置全部标记上
// 如果要改进的话,标记位置不需要new一个数组,记录最远的位置即可
// int[] me = new int[nums.length];
// me[0] = 1;
// for (int i = 0; i < me.length; i++) {
// for (int j = i + 1; j < i + 1 + nums[i] && j < me.length; j++) {
// me[j] = 1;
// }
// if (me[i] == 0) {
// return false;
// }
// }
// return true;
// 2.从最后一个位置推能不能回到初始位置
int len = nums.length;
int target = len - 1;
int i = len - 2;
while (i >= 0) {
while (i >= 0 && nums[i] < target - i) {
i--;
}
if (i < 0) {
return false;
}
target = i;
i--;
}
return true;
}
}

1005.K次取反后最大化的数组和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
// 要先从最小的负值元素翻转一次,如果还留有剩余,那就继续翻转其他负值的元素
// 如果负值翻转完毕,就只对最小的正值元素一直翻转
// 因此按照绝对值的从小到大开始排列,从后往前依次翻转负数,如果最后k还有剩余,那就翻转位置0
nums = IntStream.of(nums)
.boxed()
.sorted((o1, o2) -> Math.abs(o1) - Math.abs(o2))
.mapToInt(Integer::intValue).toArray();
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] < 0 && k > 0) {
nums[i] *= -1;
k--;
}
}
if (k % 2 == 1) {
nums[0] *= -1;
}
int result = 0;
for (int num : nums) {
result += num;
}
return result;
}
}

452. 用最少数量的箭引爆气球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int findMinArrowShots(int[][] points) {
// 对区间进行排序,当区间有重叠的时候,即ai+1<=bi的时候,射中bi即可,另外还要更新当前区间的最右位置
// 使用Integer.compare不会溢出
Arrays.sort(points, (a, b) -> {
return Integer.compare(a[0], b[0]);
});
int result = 0;
for (int i = 0; i < points.length; i++) {
while (i + 1 < points.length && points[i + 1][0] <= points[i][1]) {
points[i + 1][1] = Math.min(points[i + 1][1], points[i][1]);
i++;
}
result++;
}
return result;
}
}

435. 无重叠区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
// 这道题和452.很相似,引爆气球的箭的数量其实就是无重叠区间的数量
// 只有intervals[i][1]<=intervals[i+1][0]的时候,才是不重叠区间
Arrays.sort(intervals, (a, b) -> {
return a[0] - b[0];
});
int result = 0;
for (int i = 0; i < intervals.length; i++) {
while (i + 1 < intervals.length && intervals[i][1] > intervals[i + 1][0]) {
intervals[i+1][1] = Math.min(intervals[i][1], intervals[i + 1][1]);
result++;
i++;
}
}
return result;
}
}

763.划分字母区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public List<Integer> partitionLabels(String s) {
// 记录每一个字母出现的最远的位置
// i从0开始遍历,考虑i=0的时候,那么第一个片段至少要包括到s[0]的最远位置
// 并且第一个片段就要考虑到这中间的所有字母,依次更新第一个片段的右区间
int[] me = new int[26];
Arrays.fill(me, -1);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
me[c - 'a'] = Math.max(me[c - 'a'], i);
}
List<Integer> result = new ArrayList<>();
int start = 0;
int end = 0;
for (int i = 0; i < s.length();) {
char c = s.charAt(i);
end = Math.max(end, me[c - 'a']);
i++;
while (i <= end) {
char d = s.charAt(i);
end = Math.max(end, me[d - 'a']);
i++;
}
result.add(end - start + 1);
start = i;
end = i;

}
return result;
}
}

56. 合并区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> result = new ArrayList<>();
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
for (int i = 0; i < intervals.length; i++) {
int start = intervals[i][0];
int end = intervals[i][1];
while (i + 1 < intervals.length && end >= intervals[i + 1][0]) {
end = Math.max(end, intervals[i + 1][1]);
i++;
}
result.add(new int[] { start, end });
}
return result.toArray(new int[result.size()][]);
}
}

738.单调递增的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public int monotoneIncreasingDigits(int n) {
// 当数字:xy(其中x>y)的时候,x=x-1,y=9,数字从后往前遍历
// 注意:假如有数字1000,我们按照这个处理逻辑只能得到0900
// 所以要单独记录flag,flag之后全是9
String numStr = n + "";
int len = numStr.length();
int[] num = new int[len];
int flag = len;
for (int i = len - 1; i >= 0; i--) {
num[i] = n % 10;
n = n / 10;
}
for (int i = len - 1; i > 0; i--) {
if (num[i - 1] > num[i]) {
num[i - 1]--;
flag = i;
}
}
int result = 0;
for (int i = 0; i < len; i++) {
if (i >= flag) {
num[i] = 9;
}
result = 10 * result + num[i];
}
return result;
}
}

968.监控二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
// 后序遍历
// 把节点分为2种状态:
// 1被间接覆盖(指的是当前节点没有摄像头,但是被父节点的摄像头或者子节点的摄像头覆盖了)
// 2当前节点有摄像头
// 3当前节点没有被间接覆盖
// 状态转移:左右节点当中:
// 有3(有没有被覆盖的)就2(装摄像头)
// 有2(摄像头)就1(被覆盖)
// 其余情况(只有1,全都被间接覆盖的时候)就是3(没有被覆盖)
// 递归返回条件:为了节省摄像头,根节点的父亲要安装摄像头,所以根节点的状态应该是3
int result = 0;

public int minCameraCover(TreeNode root) {
if (3 == minCamera(root)) {
result++;
}
return result;
}

public int minCamera(TreeNode root) {
if (root.left == null && root.right == null) {
return 3;
}
int state = 0;
int leftState = 0;
int rightState = 0;
if (root.left != null) {
leftState = minCamera(root.left);
}
if (root.right != null) {
rightState = minCamera(root.right);
}
if (leftState == 3 || rightState == 3) {
result++;
state = 2;
} else if (leftState == 2 || rightState == 2) {
state = 1;
} else {
state = 3;
}
return state;
}
}

回溯算法

相关知识

回溯本质上是一种纯暴力搜索算法,常用来解决排列组合、子集问题、切割字符串、棋牌问题。

这里注意一定要画出树,再写代码。树的宽度是回溯处理的集合的大小,树的深度就是递归的深度。

回溯算法理论基础

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
void backtracking(参数){
if(终止条件/叶子节点){
收集结果;
return;
}
//以下是单层搜索的逻辑
for(集合的元素集){
处理每一层的节点;
递归函数;
回溯操作/撤销递归的效果;
}
}

回溯三部曲:

1.递归函数的参数和返回值

2.终止条件

3.单层递归/搜索的逻辑

77.组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> combine(int n, int k) {
backtracking(k, n, 1);
return result;
}

public void backtracking(int k, int n, int start) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
// 这里注意,如果直接add(path),那么增加的都是path对象,而不是path的内容
return;
}
for (int i = start; i <= n; i++) {
path.add(i);
backtracking(k, n, i + 1);
path.remove(Integer.valueOf(i));
}
}
}

考虑剪枝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> combine(int n, int k) {
backtracking(k, n, 1);
return result;
}

public void backtracking(int k, int n, int start) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
// 这里注意,如果直接add(path),那么增加的都是path对象,而不是path的内容
return;
}
// 考虑剪枝,i从start开始,最多只能搜索到n-(k-path.size())+1的位置
// 其中k-path.size()是还差几个元素
for (int i = start; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
backtracking(k, n, i + 1);
path.remove(Integer.valueOf(i));
}
}
}

216.组合总和III

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k, n, 1, 0);
return result;
}

public void backtracking(int k, int n, int start, int sum) {
if (path.size() == k && sum == n) {
result.add(new ArrayList(path));
return;
}
for (int i = start; i <= 9; i++) {
// 剪枝操作
if (path.size() + 9 - i + 1 < k) {
return;
}
path.add(i);
backtracking(k, n, i + 1, sum + i);
path.removeLast();
}
}
}

17.电话号码的字母组合

这道题需要注意,每一个for处理的都是每一层的元素的共性操作,需要注意backtracking的操作要分的清楚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
String path = "";
List<String> result = new ArrayList<>();
String[] me = { "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };

public List<String> letterCombinations(String digits) {
if (digits.length() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}

// 处理digits当中第i个元素
public void backtracking(String digits, int i) {
if (i >= digits.length()) {
result.add(new String(path));
return;
}
String data = me[digits.charAt(i) - '2'];
for (int j = 0; j < data.length(); j++) {
path += data.charAt(j) + "";
backtracking(digits, i + 1);
path = path.substring(0, path.length() - 1);
}
}
}

131.分割回文串

可以先使用动态规划把字符串当中所有的回文子串标记上,然后再进行切割和剪枝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
List<String> path = new ArrayList<>();
List<List<String>> result = new ArrayList<>();

public List<List<String>> partition(String s) {
int[][] dp = huiwen(s);
backtracking(s, 0, dp);
return result;
}

// 切割start开始的子串
public void backtracking(String s, int start, int[][] dp) {
if (start >= s.length()) {
result.add(new ArrayList(path));
return;
}
for (int i = start; i < s.length(); i++) {
if (dp[start][i] == 1) {
path.add(s.substring(start, i + 1));
backtracking(s, i + 1, dp);
path.removeLast();
} else {
continue;
}
}
}

// 使用动态规划判断是否是回文串
public int[][] huiwen(String s) {
// 1.dp[i][j]表示s的[i,j]的子串是否回文
// 2.dp[i][j]= i+1==j的时候要判断s[i]==s[j];dp[i+1][j-1]&&s[i]==s[j];
// 3.dp[i][j]=0,dp[i][i]=1
// 4.i从下到上,j从左到右
int len = s.length();
int[][] dp = new int[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = 1;
}
for (int i = len - 1; i >= 0; i--) {
for (int j = i + 1; j < len; j++) {
if (s.charAt(i) == s.charAt(j)) {
if (j == i + 1) {
dp[i][j] = 1;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
}
}
return dp;
}
}

39. 组合总和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(0, 0, target, candidates);
return result;
}

public void backtracking(int sum, int start, int target, int[] candidates) {
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length && sum + candidates[i] <= target; i++) {
path.add(candidates[i]);
backtracking(sum + candidates[i], i, target, candidates);
path.removeLast();
}
}
}

40.组合总和II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0);
return result;
}

public void backtracking(int[] candidates, int target, int start, int sum) {
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length && sum + candidates[i] <= target; i++) {
// 去重
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
path.add(candidates[i]);
backtracking(candidates, target, i + 1, sum + candidates[i]);
path.removeLast();
}
}
}

93.复原IP地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {
String path = "";
List<String> result = new ArrayList<>();

public List<String> restoreIpAddresses(String s) {
backtracking(s, 0, 1);
return result;
}

// 现在要插入第index个'.'
public void backtracking(String s, int start, int index) {
if (index == 5 && start >= s.length()) {
result.add(new String(path.substring(0, path.length() - 1)));
return;
}
for (int i = start; i < s.length() && i <= start + 3; i++) {
String str = s.substring(start, i + 1);
if (index > 4) {
return;
}
if (valid(str) == false || s.length() - i - 1 > (4 - index) * 3) {
continue;
}
String nowPath = path;
path = path + str + ".";
backtracking(s, i + 1, index + 1);
path = nowPath;
}
}

// 判断如果是把str作为其中的某一个数字的话,能否有效合法
public boolean valid(String str) {
if (str.length() >= 4) {
return false;
}
if (str.charAt(0) == '0' && str.length() > 1) {
return false;
}
int num = Integer.valueOf(str);
if (num > 255 || num < 0) {
return false;
}
return true;
}
}

78.子集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0);
return result;
}

public void backtracking(int[] nums, int start) {
if (start <= nums.length) {
result.add(new ArrayList<>(path));
}
if (start == nums.length) {
return;
}
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtracking(nums, i + 1);
path.removeLast();
}
}
}

90.子集II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtracking(nums, 0);
return result;
}

public void backtracking(int[] nums, int start) {
if (start <= nums.length) {
result.add(new ArrayList<>(path));
}
if (start == nums.length) {
return;
}
for (int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
backtracking(nums, i + 1);
path.removeLast();
}
}
}

491.递增子序列

而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑!

491. 递增子序列1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0, -101);
return result;
}

public void backtracking(int[] nums, int start, int compare) {
// 根据path元素个数剪枝
if (path.size() >= 2) {
result.add(new ArrayList<>(path));
}
if (start == nums.length) {
return;
}
HashSet<Integer> set = new HashSet<>();// 记录目前已经考虑过的元素
for (int i = start; i < nums.length; i++) {
// 根据递增序列剪枝
if (nums[i] < compare) {
continue;
}
// 根据重复元素剪枝
if (set.contains(nums[i])) {
continue;
}
set.add(nums[i]);
path.add(nums[i]);
backtracking(nums, i + 1, nums[i]);
path.removeLast();
}
}
}

46.全排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> permute(int[] nums) {
int[] used = new int[nums.length];
backtracking(nums, used);
return result;
}

public void backtracking(int[] nums, int[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i] == 1) {
continue;
}
path.add(nums[i]);
used[i] = 1;
backtracking(nums, used);
path.removeLast();
used[i] = 0;
}
}
}

47.全排列 II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
int[] used = new int[nums.length];
backtracking(nums, used);
return result;
}

public void backtracking(int[] nums, int[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// 处理重复元素的剪枝
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) {
continue;
}
if (used[i] == 1) {
continue;
}
used[i] = 1;
path.add(nums[i]);
backtracking(nums, used);
used[i] = 0;
path.removeLast();
}
}
}

37. 解数独

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Solution {
public void solveSudoku(char[][] board) {
backtracking(board);
}

public boolean backtracking(char[][] board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
// 在[i][j]位置上试9个数
for (int k = 1; k <= 9; k++) {
if (vaild(board, i, j, k)) {
// 当前位置填k可以,那就填上k
board[i][j] = (char) (k + '0');
// 当前位置填k是可以的,继续递归下一个位置
boolean success = backtracking(board);
// 如果递归接下来的位置都成功,那么棋盘就填完了
if (success) {
return true;
}
// 回溯刚才填入的k
board[i][j] = '.';
}
} // for(k)
// [i][j]整个位置9个数填着都不行,那就返回false
// (因为但凡有一个数字可行的,可以进行下去的,都会在递归下一个位置当中,不会到这里来)
return false;
}
}
} // for(i)
// 全都填完,返回true
return true;
}

// 在棋盘[i][j]位置处添加k是否合法
public boolean vaild(char[][] board, int i, int j, int k) {
char charK = (char) (k + '0');
for (int m = 0; m < 9; m++) {
if (board[m][j] == charK) {
return false;
}
if (board[i][m] == charK) {
return false;
}
}
// 找到所在块的起点
int newI = (i / 3) * 3;
int newJ = (j / 3) * 3;
for (int m = newI; m < newI + 3; m++) {
for (int n = newJ; n < newJ + 3; n++) {
if (board[m][n] == charK) {
return false;
}
}
}
return true;
}
}

51. N皇后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Solution {
List<List<String>> result = new ArrayList<>();

public List<List<String>> solveNQueens(int n) {
int[][] board = new int[n][n];
backtracking(board, n, 0);
return result;
}

// 在第i行加入皇后
public void backtracking(int[][] board, int n, int i) {
if (i == n) {
result.add(convert(board, n));
return;
}
for (int j = 0; j < n; j++) {
// 剪枝
if (board[i][j] == 1) {
continue;
}
// 当前位置有效,就继续递归下去
if (vaild(board, i, j, n)) {
board[i][j] = 1;
backtracking(board, n, i + 1);
board[i][j] = 0;
}
}
}

// 把int[][]形式表示的棋盘转化成List<String>
public List<String> convert(int[][] board, int n) {
List<String> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
String str = "";
for (int j = 0; j < n; j++) {
if (board[i][j] == 1) {
str = str + "Q";
} else {
str = str + ".";
}
}
result.add(str);
}
return result;
}

// 在当前棋盘当中,[i][j]位置放入皇后是否合法
public boolean vaild(int[][] board, int i, int j, int n) {
// 因为是一行行加入的皇后,所以只需要考虑列,左上,右上的方向
for (int m = 0; m < n; m++) {
if (board[m][j] == 1) {
return false;
}
}
// 判断斜线
for (int newI = i, newJ = j; newI >= 0 && newJ >= 0; newI--, newJ--) {
if (board[newI][newJ] == 1) {
return false;
}
}
for (int newI = i, newJ = j; newI >= 0 && newJ < n; newI--, newJ++) {
if (board[newI][newJ] == 1) {
return false;
}
}
return true;
}
}

332.重新安排行程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// class Solution {
// 这个方法也可以,但是超时了
// List<String> result = new ArrayList<>();

// public List<String> findItinerary(List<List<String>> tickets) {
// Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
// int[] used = new int[tickets.size()];
// result.add("JFK");
// backtracking(tickets, "JFK", used);
// return result;
// }

// // 把回溯算法设置为布尔值是因为我们只需要找到一个结果即可
// public boolean backtracking(List<List<String>> tickets, String from, int[] used) {
// if (result.size() == tickets.size() + 1) {
// return true;
// }
// for (int i = 0; i < tickets.size(); i++) {
// if (used[i] == 0 && tickets.get(i).get(0).equals(from)) {
// used[i] = 1;
// result.add(tickets.get(i).get(1));
// if (backtracking(tickets, result.getLast(), used)) {
// return true;
// } else {
// used[i] = 0;
// result.removeLast();
// }
// }
// }
// return false;
// }
// }

class Solution {
private List<String> res = new ArrayList<>();
private Map<String, Map<String, Integer>> map = new TreeMap<String, Map<String, Integer>>();

private boolean backTracking(int ticketNum) {
if (res.size() == ticketNum + 1) {
return true;
}
String last = res.getLast();
if (map.containsKey(last)) {
for (Map.Entry<String, Integer> target : map.get(last).entrySet()) {
int count = target.getValue();
if (count > 0) {
res.add(target.getKey());
target.setValue(count - 1);
if (backTracking(ticketNum))
return true;
res.removeLast();
target.setValue(count);
}
}
}
return false;
}

public List<String> findItinerary(List<List<String>> tickets) {
// 把tickets内容加入到map当中
// map的结构是:map(出发,map(终点,重复次数)),同一出发点的会按照字典排序放入终点
for (List<String> t : tickets) {
Map<String, Integer> temp = new TreeMap<>();// 升序map
if (map.containsKey(t.get(0))) {
temp = map.get(t.get(0));
temp.put(t.get(1), temp.getOrDefault(t.get(1), 0) + 1);
} else {
temp.put(t.get(1), 1);
}
// 因为map是升序map,这一步的时候会按照temp的key排序,也就是字典排序
map.put(t.get(0), temp);
}
res.add("JFK");
backTracking(tickets.size());
return res;
}
}

79. 单词搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
boolean result = false;

public boolean exist(char[][] board, String word) {
int m = board.length;
int n = board[0].length;
int[][] isUsed = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
func(board, i, j, word, 0, isUsed);
}
}
return result;
}

// 从棋盘的[i,j]位置开始寻找以word当中k位置为起点的字符串
public void func(char[][] board, int i, int j, String word, int k, int[][] isUsed) {
if (i >= board.length || j >= board[0].length || i < 0 || j < 0) {
return;
}
if (isUsed[i][j] == 1) {
return;
}
if (k == word.length() - 1 && board[i][j] == word.charAt(k)) {
result = true;
return;
}
isUsed[i][j] = 1;
if (board[i][j] == word.charAt(k)) {
func(board, i + 1, j, word, k + 1, isUsed);
func(board, i - 1, j, word, k + 1, isUsed);
func(board, i, j + 1, word, k + 1, isUsed);
func(board, i, j - 1, word, k + 1, isUsed);
} else {
isUsed[i][j] = 0;
return ;
}
isUsed[i][j] = 0;
}
}

数学

1356. 根据数字二进制下 1 的数目排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int[] sortByBits(int[] arr) {
// 判断一个数字的二进制有几个1,n=n&(n-1),几次循环到n==0就是有几位1
// 原理是n-1会把n当中最后一位1变成0,之后的所有0变成1,按位与之后把最后一个1变成了0
return Arrays.stream(arr).boxed().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer num1, Integer num2) {
int a = count1(num1);
int b = count1(num2);
return (a == b) ? Integer.compare(num1, num2) : Integer.compare(a, b);
}
}).mapToInt(Integer::intValue).toArray();
}

public int count1(int num) {
int count = 0;
while (num != 0) {
num = num & (num - 1);
count++;
}
return count;
}
}

7. 整数反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int reverse(int x) {
int flag = x >= 0 ? 1 : -1;
int result = 0;
x = Math.abs(x);
while (x > 0) {
int i = x % 10;
// 处理溢出
if (result > Integer.MAX_VALUE / 10 ) {
return 0;
}
result = result * 10 + i;
x = x / 10;
}
return flag * result;
}
}

136. 只出现一次的数字

异或即可

461. 汉明距离

先按位异或,再通过z和(z-1)与来判断有多少个1

338. 比特位计数

使用x&(x-1)来确定1的个数

66. 加一

如果是99..9,那么结果数组就要多一位

单调栈

739. 每日温度

我们在遍历到i位置的时候,需要查看i之前的元素有没有比i上的元素小的,这就是单调栈的由来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
// 单调栈
// 因为要计算位置的差,所以单调栈当中存储的是位置,不是元素
// 栈顶遇到比它大的元素,就弹栈
int len = temperatures.length;
int[] result = new int[len];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < len; i++) {
// 弹栈
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int j = stack.pop();
result[j] = i - j;
}
// 压栈
stack.push(i);
}
// 把栈的内容都倒出来
while (!stack.isEmpty()) {
result[stack.pop()] = 0;
}
return result;
}
}

496.下一个更大元素 I

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
// 导致会有O(mn)的时间复杂度的原因是,nums1[i] == nums2[j]的寻找
// 为了减少时间复杂度,使用map记录(nums1[i],i)
// 单调栈只考虑nums2的元素
Stack<Integer> stack = new Stack<>();
stack.push(nums2[0]);
int len1 = nums1.length;
int len2 = nums2.length;
int[] result = new int[len1];
Arrays.fill(result, -1);
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < len1; i++) {
map.put(nums1[i], i);
}
int j = 1;
while (j < len2) {
while (!stack.isEmpty() && stack.peek() < nums2[j]) {
int num = stack.pop();
if (map.containsKey(num)) {
result[map.get(num)] = nums2[j];
}
}
stack.push(nums2[j]);
j++;
}
return result;
}
}

503.下一个更大元素II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int[] nextGreaterElements(int[] nums) {
// 意味着单调栈需要录入数组两遍
Stack<Integer> stack = new Stack<>();
int len = nums.length;
int[] result = new int[len];
Arrays.fill(result, -1);
stack.add(0);
for (int j = 1; j < len * 2; j++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[j % len]) {
int num = stack.pop();
result[num] = nums[j % len];
}
stack.push(j % len);
}
return result;
}
}

84.柱状图中最大的矩形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public int largestRectangleArea(int[] heights) {
// 双指针+单调栈:
// right[i]表示i右侧的第一个更小的元素所在的位置
// left同理
// size[i]表示以i位置的高度为基准,形成的矩形的最大面积
// size[i]=(right[i]-left[i]-1)*heights[i]
// 最后的结果是在整个size数组当中选个最大的数值
int len = heights.length;
int[] right = new int[len];
int[] left = new int[len];
Arrays.fill(right, len);
Arrays.fill(left, -1);
Stack<Integer> rightStack = new Stack<>();
Stack<Integer> leftStack = new Stack<>();
for (int i = 0; i < len; i++) {
while (!rightStack.isEmpty() && heights[rightStack.peek()] > heights[i]) {
int index = rightStack.pop();
right[index] = i;
}
rightStack.push(i);
}
for (int i = len - 1; i >= 0; i--) {
while (!leftStack.isEmpty() && heights[leftStack.peek()] > heights[i]) {
int index = leftStack.pop();
left[index] = i;
}
leftStack.push(i);
}
int[] size = new int[len];
int result = 0;
for (int i = 0; i < len; i++) {
size[i] = (right[i] - left[i] - 1) * heights[i];
System.out.print(size[i] + " ");
result = Math.max(result, size[i]);
}
return result;
}
}

二叉树

94.二叉树的中序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
// 非递归写法
// 因为中序遍历的程序访问节点顺序≠遍历顺序,所以需要一个指针cur指向当前遍历的节点
// 建议照着示例的树来写逻辑
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}

144.二叉树的前序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
// 非递归写法,遍历顺序和处理顺序一致,不需要cur单独遍历
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
if (temp == null) {
continue;
}
result.add(temp.val);
stack.push(temp.right);
stack.push(temp.left);
}
return result;
}
}

145.二叉树的后序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
// 非递归写法,reverse前序遍历即可
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
if (temp == null) {
continue;
}
result.add(temp.val);
stack.push(temp.left);
stack.push(temp.right);
}
Collections.reverse(result);
return result;
}
}

102.二叉树的层序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
// 使用队列来存储节点,难点在于每一层最后一个元素的判断
Deque<TreeNode> queue = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if (root == null) {
return result;
}
queue.add(root);
TreeNode last = root;// 记录每一层的最后一个位置
while (!queue.isEmpty()) {
TreeNode temp = queue.removeFirst();
path.add(temp.val);
if (temp.left != null) {
queue.add(temp.left);
}
if (temp.right != null) {
queue.add(temp.right);
}
if (temp == last) {
// 进入到下一层
last = queue.peekLast();
result.add(path);
path = new ArrayList<>();
}
}
return result;
}
}

107. 二叉树的层序遍历 II

层序遍历加入result之后,再把result翻转即可

199. 二叉树的右视图

即返回每一层的最后一个节点

637. 二叉树的层平均值

维护一个变量累计每一层的val即可

这里注意关于Double的处理。题目当中TreeNode.val是整数,如果sum也定义成int再double(sum)强转的话,sum可能会溢出。所以不如直接把sum设为Double:

1
Double sum = 0.0;

429. N 叉树的层序遍历

只需要注意遍历n叉树的子节点和二叉树的不同即可,具体要看题目里面是怎么定义子节点的

515. 在每个树行中找最大值

层序遍历,每一层只需要维护一个最大值即可

注意max的初始化:

1
int max = Integer.MIN_VALUE;

116.填充每个节点的下一个右侧节点指针

这个函数的意思是把以root为根节点的子树的next指针都处理完毕,最后返回root。

需要维护pre指针指向当前节点temp的前序节点,并且规定每一层第一个节点为temp时,pre是空

117. 填充每个节点的下一个右侧节点指针 II

同上题

104. 二叉树的最大深度

可以通过层序遍历,记录层数即可

111. 二叉树的最小深度

可以通过层序遍历,最先遇到叶子节点的层数就是最小深度

226. 翻转二叉树

前序遍历,递归写法即可

101. 对称二叉树

前序遍历,递归比较的是compare(root1.left, root2.right) && compare(root1.right, root2.left);

559. N 叉树的最大深度

递归写法即可

222. 完全二叉树的节点个数

递归即可

257. 二叉树的所有路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
List<String> result = new ArrayList<>();
String path = "";

public List<String> binaryTreePaths(TreeNode root) {
// 前序遍历
paths(root);
return result;
}

public void paths(TreeNode root) {
if (root.left == null && root.right == null) {
path = path + root.val;
result.add(path);
return;
}
path = path + root.val + "->";
String temp = path;
if (root.left != null) {
paths(root.left);
}
path = temp;
if (root.right != null) {
paths(root.right);
}
}
}

404. 左叶子之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
int result = 0;

// 递归即可
public int sumOfLeftLeaves(TreeNode root) {
sum(root);
return result;
}

public void sum(TreeNode root) {
if (root == null) {
return;
}
if (root.left != null && root.left.left == null && root.left.right == null) {
result += root.left.val;
}
if (root.left != null) {
sum(root.left);
}
if (root.right != null) {
sum(root.right);
}
}
}

513. 找树左下角的值

层序遍历,每到新的一层更新最左下的节点即可

112. 路径总和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 递归
if (root == null) {
return false;
}
return has(root, targetSum);
}

public boolean has(TreeNode root, int target) {
if (root.left == null && root.right == null) {
return root.val == target;
}
boolean left = false;
if (root.left != null) {
left = has(root.left, target - root.val);
}
boolean right = false;
if (root.right != null) {
right = has(root.right, target - root.val);
}
return left || right;
}
}

110. 平衡二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
if (Math.abs(compute(root.left) - compute(root.right)) >= 2) {
return false;
} else {
return isBalanced(root.left) && isBalanced(root.right);
}
}

// 计算子树当中的深度
public int compute(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(compute(root.left), compute(root.right)) + 1;
}
}

617. 合并二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
return merge(root1, root2);
}

public TreeNode merge(TreeNode root1, TreeNode root2) {
if (root1 == null) {
return root2;
}
if (root2 == null) {
return root1;
}
TreeNode result = new TreeNode(0);
result.val = root1.val + root2.val;
result.left = merge(root1.left, root2.left);
result.right = merge(root1.right, root2.right);
return result;
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
// 后序遍历
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == p || root == q || root == null) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// left!=null说明碰到了p或者q,right!=null同理
if (left != null && right != null) {
return root;
} else if (left == null && right != null) {
return right;
} else {
// left!=null&&right==null的时候,返回left
// left==null&&right==null的时候,返回null
return left;
}
}
}

501. 二叉搜索树中的众数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
// 通过中序遍历获得递增的val排列,然后使用count在递增有序链表当中查找众数
List<Integer> list = new ArrayList<>();

public int[] findMode(TreeNode root) {
search(root);
int count = 0;
int maxCount = 0;
int pre = list.get(0);
List<Integer> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
int temp = list.get(i);
if (temp == pre) {
count++;
} else {
count = 1;
}
if (count > maxCount) {
result.clear();
result.add(temp);
maxCount = count;
} else if (count == maxCount) {
result.add(temp);
}
pre = temp;
}
return result.stream().mapToInt(Integer::intValue).toArray();
}

public void search(TreeNode root) {
if (root.left != null) {
search(root.left);
}
list.add(root.val);
if (root.right != null) {
search(root.right);
}
}
}

530. 二叉搜索树的最小绝对差

中序遍历,获得递增序列,再比较得出最小的差值即可

98. 验证二叉搜索树

中序遍历得到递增链表

235. 二叉搜索树的最近公共祖先

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
// 后序遍历,如果root的val在p和q之间,那么p和q的公共祖先就是root
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right, p, q);
}
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q);
}
return root;
}
}

701. 二叉搜索树中的插入操作

前序遍历,把所有插入的节点全放到叶子位置即可

538. 把二叉搜索树转换为累加树

右中左的顺序遍历即可

108. 将有序数组转换为二叉搜索树

为什么形如以下的写法不会建立一个完整的树:在递归调用中,你虽然创建了新的 TreeNode 并将它赋值给 root,但这个 root 只在当前递归调用的栈帧中有效。一旦递归返回,这个 root 的改变并不会反映到原始调用者或上一层的递归调用中。因此,你需要通过其他方式(如返回新创建的节点)来确保树的构建是连续的。

1
2
3
4
5
6
7
8
9
public void add(int[] nums, TreeNode root, int start, int end) {
if (end < start) {
return;
}
int mid = (end - start) / 2 + start;
root = new TreeNode(nums[mid]);
add(nums, root.left, start, mid - 1);
add(nums, root.right, mid + 1, end);
}

正确写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return add(nums, 0, nums.length - 1);
}

// 处理[start,end]的元素,root存放当前区间的中间位置
// 注意需要返回TreeNode
public TreeNode add(int[] nums, int start, int end) {
if (end < start) {
return null;
}
int mid = (end - start) / 2 + start;
TreeNode root = new TreeNode(nums[mid]);
root.left = add(nums, start, mid - 1);
root.right = add(nums, mid + 1, end);
return root;
}
}

106. 从中序与后序遍历序列构造二叉树

找到inorder的[inStart,inEnd]这些元素当中哪一个元素位于postOrder的最后,这个元素就是root

为了降低时间复杂度,把postOrder转为map

105. 从前序与中序遍历序列构造二叉树

inorder[inStart,inEnd]的元素当中位于preOrder里面最前面的就是root

为了减少查找的时间度,把preOrder转成map

700. 二叉搜索树中的搜索

递归查找即可

654. 最大二叉树

按照题目意思即可,注意考察nums的区间的开闭

655. 输出二叉树

按照题目叙述即可

450.删除二叉搜索树中的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
// 前序遍历,把左子树放到右子树当中最小的一个值下面,然后让右子树替代被删除的节点
public TreeNode deleteNode(TreeNode root, int key) {
return delete(root, key);
}

public TreeNode delete(TreeNode root, int key) {
if (root == null) {
return null;
}
if (root.val == key) {
if (root.left == null && root.right == null) {
root = null;
} else if (root.left == null && root.right != null) {
root = root.right;
} else if (root.left != null && root.right == null) {
root = root.left;
} else {
TreeNode min = root.right;
while (min.left != null) {
min = min.left;
}
min.left = root.left;
root = root.right;
}
}
if (root.val < key) {
root.right = delete(root.right, key);
}
if (root.val > key) {
root.left = delete(root.left, key);
}
return root;
}
}

669. 修剪二叉搜索树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
// 后序遍历
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) {
return null;
}
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
// 删除当前节点,把左子树挂到右子树上,把当前节点换成右子树
if (root.val > high || root.val < low) {
if (root.right != null) {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
root = root.right;
return root;
} else {
return root.left;
}
}
return root;
}
}

114. 二叉树展开为链表

后序遍历,把右子树放到左子树的最右孩子上,再把左子树放到右子树上

129. 求根节点到叶节点数字之和

前序遍历即可

103. 二叉树的锯齿形层序遍历

层序遍历之后再翻转即可

543. 二叉树的直径

递归求取root的深度,其直径即左子树深度+右子树深度

230. 二叉搜索树中第 K 小的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int kthSmallest(TreeNode root, int k) {
return search(root, k);
}

public int search(TreeNode root, int k) {
int leftNodes = countNodes(root.left);
if (k == 1 + leftNodes) {
return root.val;
} else if (k < 1 + leftNodes) {
return search(root.left, k);
} else {
return search(root.right, k - leftNodes - 1);
}
}

// 计算root子树当中节点的个数
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int leftNodes = countNodes(root.left);
int rightNodes = countNodes(root.right);
return leftNodes + rightNodes + 1;
}
}

437. 路径总和 III

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
int result = 0;

public int pathSum(TreeNode root, int targetSum) {
search(root, targetSum, false);
return result;
}

// is标记当前节点是否一定要加入路径计算当中
public void search(TreeNode root, long targetSum, boolean is) {
if (root == null) {
return;
}
if (targetSum == root.val) {
result++;
}
if (is == false) {
search(root.left, targetSum - root.val, true);
search(root.left, targetSum, false);
search(root.right, targetSum - root.val, true);
search(root.right, targetSum, false);
} else {
search(root.left, targetSum - root.val, true);
search(root.right, targetSum - root.val, true);
}
}
}

95. 不同的二叉搜索树 II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public List<TreeNode> generateTrees(int n) {
// 递归
return generate(1, n);
}

public List<TreeNode> generate(int start, int end) {
List<TreeNode> result = new ArrayList<>();
if (start > end) {
result.add(null);
return result;
}
for (int i = start; i <= end; i++) {
List<TreeNode> leftTrees = generate(start, i - 1);
List<TreeNode> rightTrees = generate(i + 1, end);
for (TreeNode leftNode : leftTrees) {
for (TreeNode rightNode : rightTrees) {
TreeNode root = new TreeNode(i);
root.left = leftNode;
root.right = rightNode;
result.add(root);
}
}
}
return result;
}
}

872. 叶子相似的树

中序遍历即可

注意使用 != 操作符来比较两个 Integer 对象时,实际上是在比较它们是否引用了同一个对象Integer 对象

对于小数值(通常是-128 到127 之间的整数)会被缓存,这样相同数值的 Integer 对象实际上是相同的对象实例

但对于较大的数值,例如200,每个 Integer 对象都会是独立创建的,即使它们的值相同

这里list的元素,即list2.get(i)是Integer,更加可靠的方式是通过对象的比较方法,即equals,如下:

1
if (!list1.get(i).equals(list2.get(i)))