LeetCode刷题指南
- 算法
- 双指针
- 633[平方数之和](https://leetcode-cn.com/problems/sum-of-square-numbers/)
- 345[反转字符串中的元音字母](https://leetcode-cn.com/problems/reverse-vowels-of-a-string/)_
- 680[验证回文字符串](https://leetcode-cn.com/problems/valid-palindrome-ii/)_
- 141[环形链表](https://leetcode-cn.com/problems/linked-list-cycle/)
- 524[通过删除字母匹配到字典里最长单词](https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/)
- 贪心思想
- 435.[无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)
- 56. [合并区间](https://leetcode-cn.com/problems/merge-intervals/)
- 406. [根据身高重建队列](https://leetcode-cn.com/problems/queue-reconstruction-by-height/)
- 122. [买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
- 605. [种花问题](https://leetcode-cn.com/problems/can-place-flowers/)
- 763. [划分字母区间](https://leetcode-cn.com/problems/partition-labels/)
- 分治
- 动态规划
- 斐波那契数列
- 70.[爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/)
- 198.[打家劫舍](https://leetcode-cn.com/problems/house-robber/)
- 213.[打家劫舍 II](https://leetcode-cn.com/problems/house-robber-ii/)
- 矩阵路径
- 64.[最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/)
- 数组区间
- 413.[等差数列划分](https://leetcode-cn.com/problems/arithmetic-slices/)
- 分割整数
- 343.[整数拆分](https://leetcode-cn.com/problems/integer-break/)
- 279.[完全平方数](https://leetcode-cn.com/problems/perfect-squares/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china)
- 91.[解码方法](https://leetcode-cn.com/problems/decode-ways/)
- 0-1 背包
- 416. [分割等和子集](https://leetcode-cn.com/problems/partition-equal-subset-sum/)转换成了sum/2体积的0-1背包问题
- 494. [目标和](https://leetcode-cn.com/problems/target-sum/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china)
- 139. [单词拆分](https://leetcode-cn.com/problems/word-break/)
- 474. [一和零](https://leetcode-cn.com/problems/ones-and-zeroes/)
- 股票交易
- 309. [最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- 714. [买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
- 123. [买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
- 188. [买卖股票的最佳时机 IV](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/)
- *子序列*
- 376. [摆动序列](https://leetcode-cn.com/problems/wiggle-subsequence/)
- 1143.[最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/)
- 字符串编辑
- 72. [编辑距离](https://leetcode-cn.com/problems/edit-distance/)
- 10. [正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching/)
- 44. [通配符匹配](https://leetcode-cn.com/problems/wildcard-matching/)
- 650. [只有两个键的键盘(复制粘贴字符)](https://leetcode-cn.com/problems/2-keys-keyboard/)
- 数学
- 204. [计数质数](https://leetcode-cn.com/problems/count-primes/)
- 172. [阶乘后的零](https://leetcode-cn.com/problems/factorial-trailing-zeroes/)
- 415. [字符串相加](https://leetcode-cn.com/problems/add-strings/)
- 462. [最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/)
- 238. [除自身以外数组的乘积](https://leetcode-cn.com/problems/majority-element/)
- 排序
- 二分查找
- 69. [x 的平方根](https://leetcode-cn.com/problems/sqrtx/)
- 744. [寻找比目标字母大的最小字母](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/)
- 34. [在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
- 540. [有序数组中的单一元素](https://leetcode-cn.com/problems/single-element-in-a-sorted-array/)
- 153. [寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/)
- 4. [寻找两个正序数组的中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/he-bing-yi-hou-zhao-gui-bing-guo-cheng-zhong-zhao-/)
- 搜索
- BFS
- 127. [单词接龙](https://leetcode-cn.com/problems/word-ladder/)
- DFS
- 695. [岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/)
- 200. [岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
- 463. [岛屿的周长](https://leetcode-cn.com/problems/island-perimeter/)
- 130. [被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/)
- 547. [省份数量](https://leetcode-cn.com/problems/number-of-provinces/)
- 417. [太平洋大西洋水流问题](https://leetcode-cn.com/problems/pacific-atlantic-water-flow/)
- Backtracking
- 17. [电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
- 79. [单词搜索](https://leetcode-cn.com/problems/word-search/)
- 257. [二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/)
- 排列回溯
- 46. [全排列](https://leetcode-cn.com/problems/permutations/)
- 47. [全排列 II](https://leetcode-cn.com/problems/permutations-ii/)
- 组合回溯
- 77. [组合](https://leetcode-cn.com/problems/combinations/)
- 39. [组合总和](https://leetcode-cn.com/problems/combination-sum/description/)
- 40. [组合总和 II](https://leetcode-cn.com/problems/combination-sum-ii/)
- 216. [组合总和 III](https://leetcode-cn.com/problems/combination-sum-iii/)
- 字符串回溯分割
- 93. [复原 IP 地址](https://leetcode-cn.com/problems/restore-ip-addresses/)
- 131. [分割回文串](https://leetcode-cn.com/problems/palindrome-partitioning/)
- 棋谱填空
- 37. [解数独](https://leetcode-cn.com/problems/sudoku-solver/)
- 51. [N 皇后](https://leetcode-cn.com/problems/n-queens/)
- 1424. [对角线遍历 II](https://leetcode-cn.com/problems/diagonal-traverse-ii/)
- 数据结构
- 树
- 437. [路径总和 III](https://leetcode-cn.com/problems/path-sum-iii/description/)
- 572. [另一个树的子树](https://leetcode-cn.com/problems/subtree-of-another-tree/description/)_
- 111. [二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/description/)_
- 110. [平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/)_
- 98. [验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)
- 958.[完全二叉树校验](https://leetcode-cn.com/problems/check-completeness-of-a-binary-tree/solution/er-cha-shu-de-wan-quan-xing-jian-yan-by-leetcode/):和树的宽度解法很像
- 662. [二叉树最大宽度](https://leetcode-cn.com/problems/maximum-width-of-binary-tree/submissions/)
- 337. [打家劫舍 III](https://leetcode-cn.com/problems/house-robber-iii/description/) 间隔遍历
- 层次遍历
- 非递归实现二叉树的前序遍历
- 非递归实现二叉树的后序遍历
- 非递归实现二叉树的中序遍历
- 105. [从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/)
- 二叉树的z字形遍历 面试官说:不使用队列,想想其他数据结构
- 669. [修剪二叉搜索树](https://leetcode-cn.com/problems/trim-a-binary-search-tree/description/)
- 230. [二叉搜索树中第K小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/description/)
- 538. [把二叉搜索树转换为累加树](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/description/)
- 129.[求根节点到叶节点数字之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/solution/qiu-gen-dao-xie-zi-jie-dian-shu-zi-zhi-he-by-leetc/)
- 1530. [好叶子节点对的数量](https://leetcode-cn.com/problems/number-of-good-leaf-nodes-pairs/)
- 687. [最长同值路径](https://leetcode-cn.com/problems/longest-univalue-path/)
- 124. [二叉树中的最大路径和](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/)
- 235. [二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/)_
- 236. [二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)
- 109. [有序链表转换二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/description/)
- 653. [两数之和 IV - 输入 BST](https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/description/)_
- 208. [实现 Trie (前缀树)](https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/)
- 677. [键值映射](https://leetcode-cn.com/problems/map-sum-pairs/description/)
- 440. [字典序的第K小数字](https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order/)
- 字符串
- 647 [回文字符串](https://leetcode-cn.com/problems/palindromic-substrings/)
- 205. [同构字符串](https://leetcode-cn.com/problems/isomorphic-strings/)_
- 9. [回文数](https://leetcode-cn.com/problems/palindrome-number/)_
- 696[计数二进制](https://leetcode-cn.com/problems/count-binary-substrings/)_
- 567. [字符串的排列](https://leetcode-cn.com/problems/permutation-in-string/)
- 76. [最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)
- 402. [移掉 K 位数字](https://leetcode-cn.com/problems/remove-k-digits/)
- 1405. [最长快乐字符串](https://leetcode-cn.com/problems/longest-happy-string/)
- 小点补充
- 链表
- 160. [相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/)_
- NC3 [链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4)
- 206. [反转链表](https://leetcode-cn.com/problems/reverse-linked-list/description/)_
- 92. [反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)
- 25. [K 个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/)
- 61. [旋转链表](https://leetcode-cn.com/problems/rotate-list/)
- 19.[删除链表的倒数第 n 个节点](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/)
- 82. [删除排序链表中的重复元素 II](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/)
- 24. [两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs/)
- 148. [排序链表](https://leetcode-cn.com/problems/sort-list/)归并排序(递归)
- 23. [合并K个升序链表](https://leetcode-cn.com/problems/merge-k-sorted-lists/)
- 143. [重排链表](https://leetcode-cn.com/problems/reorder-list/)
- 445. [两数相加 II](https://leetcode-cn.com/problems/add-two-numbers-ii/)
- 234. [回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)_
- 725. [分隔链表](https://leetcode-cn.com/problems/split-linked-list-in-parts/)
- 328. [奇偶链表](https://leetcode-cn.com/problems/odd-even-linked-list/)
- 排序奇升偶降链表
- 栈和队列
- 哈希表
- 数组与矩阵
- 565. [数组嵌套](https://leetcode-cn.com/problems/array-nesting/)
- 162. [寻找峰值](https://leetcode-cn.com/problems/find-peak-element/)
- 31. [下一个排列](https://leetcode-cn.com/problems/next-permutation/)
- 53. [最大子序和](https://leetcode-cn.com/problems/maximum-subarray/)_
- 862. [和至少为 K 的最短子数组](https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/)
- 209. [长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/chang-du-zui-xiao-de-zi-shu-zu-by-leetcode-solutio/):和大于target的连续子序列的最小长度。
- 一个无序数组,找到一个数,左边都比他小,右边都比他大
- 421. [数组中两个数的最大异或值](https://leetcode-cn.com/problems/maximum-xor-of-two-numbers-in-an-array/)
- 求区间最小数x区间和的最大值
- 769. [最多能完成排序的块](https://leetcode-cn.com/problems/max-chunks-to-make-sorted/description/) 数组分隔
- 303. [区域和检索 - 数组不可变](https://leetcode-cn.com/problems/range-sum-query-immutable/)
- 304. [二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/)
- 240. [搜索二维有序矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/description/)
- 378. [有序矩阵中第 K 小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/description/)
- 48. [旋转图像](https://leetcode-cn.com/problems/rotate-image/)
- 54. [螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix/)
- 766. [托普利茨矩阵](https://leetcode-cn.com/problems/toeplitz-matrix/description/)_
- 图
- 易考点补充
刷题流程参照link
算法
双指针
633平方数之和
限制开始结束范围(升序) 根据结果值微调
public boolean judgeSquareSum(int c) {
int start = 0,end=(int)Math.sqrt(c);
while(start<=end){
int sum = start*start+end*end;
if(sum==c){
return true;
}else if(sum>c){
end--;
}else{
start++;
}
}
return false;
}
345反转字符串中的元音字母_
走两边向中间开始找
private static final HashSet<Character> vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U'));
public String reverseVowels(String s) {
if(s==null)return null;
int startPtr = 0,endPtr = s.length()-1;
char[] chars = new char[s.length()];
while(startPtr<=endPtr){
char sChar = s.charAt(startPtr);
char eChar = s.charAt(endPtr);
if(!vowels.contains(sChar)){
chars[startPtr++] = sChar;
}else if(!vowels.contains(eChar)){
chars[endPtr--] = eChar;
}else{
chars[startPtr++] = eChar;
chars[endPtr--] = sChar;
}
}
return new String(chars);
}
680验证回文字符串_
最多允许删一个字符 当首次出现不相等时导致两种可能
public boolean validPalindrome(String s) {
for(int i=0,j=s.length()-1;i<j;i++,j--){
if(s.charAt(i)!=s.charAt(j)){
return isPalidrome(s,i+1,j)||isPalidrome(s,i,j-1);
}
}
return true;
}
private boolean isPalidrome(String s,int startPtr,int endPtr){
while(startPtr<endPtr){
if(s.charAt(startPtr++)!=s.charAt(endPtr--)){
return false;
}
}
return true;
}
141环形链表
有环的话,快的肯定能追上慢的
public boolean hasCycle(ListNode head) {
if(head==null)return false;
ListNode slow = head,fast=head.next;
while(slow!=null&&fast!=null&&fast.next!=null){
if(slow==fast)return true;
slow = slow.next;
fast = fast.next.next;
}
return false;
}
524通过删除字母匹配到字典里最长单词
public String findLongestWord(String s, List<String> dictionary) {
String max_str = "";
for(String str:dictionary){
if(isSubSequence(str,s)){
if(str.length()>max_str.length()||(str.length()==max_str.length()&&str.compareTo(max_str)<0)){
max_str = str;
}
}
}
return max_str;
}
//判断s是否按照str字符出现的顺序包含
private boolean isSubSequence(String str,String s){
int j = 0;
for(int i =0;j<str.length()&&i<s.length();i++){
if(str.charAt(j)==s.charAt(i)){
j++;
}
}
return j == str.length();
}
贪心思想
435.无重叠区间
public int eraseOverlapIntervals(int[][] intervals) {
if(intervals.length==0)return 0;
Arrays.sort(intervals,Comparator.comparingInt(o->o[1]));
int end = intervals[0][1];
int cnt = 1; //第一个元素 end是最小的,之前排过序
for(int[]item:intervals){
if(item[0]>=end){ //起始比前一个end大,说明没有交叉
end = item[1];
cnt++;
}
}
return intervals.length-cnt;
}
使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return (o1[1] < o2[1]) ? -1 : ((o1[1] == o2[1]) ? 0 : 1);
}
});
和扎气球的想法是一样的,找非重叠区域,边界相同的为重叠
452.用最少数量的箭引爆气球
646.最长数对链
56. 合并区间
和扎气球问题挺像的
public int[][] merge(int[][] intervals) {
if(intervals.length==0){
return new int[0][2];
}
Arrays.sort(intervals,(a,b)->a[0]-b[0]);
List<int[]> list = new ArrayList();
for(int i=0;i<intervals.length;i++){
int l = intervals[i][0];
int r = intervals[i][1];
if(i==0||list.get(list.size()-1)[1]<l){
list.add(intervals[i]);
}else{
list.get(list.size()-1)[1] = Math.max(r,list.get(list.size()-1)[1]);
}
}
return list.toArray(new int[list.size()][]);
}
406. 根据身高重建队列
按照身高降序 相同则按照k升序 前面的身高都比当前高,然后就是依据k调整位置
public int[][] reconstructQueue(int[][] people) {
if(people.length==0||people[0].length==0)return null;
//按照身高降序 相同则按照k升序
Arrays.sort(people,(p1,p2)->p1[0]==p2[0]?p1[1]-p2[1]:p2[0]-p1[0]);
List<int[]> list = new ArrayList<>();
for(int[]p:people){
list.add(p[1],p); //依据k调整位置
}
return list.toArray(new int[list.size()][]);
}
122. 买卖股票的最佳时机 II
可多次买卖(再次买之前得卖掉),有利可图就给加上
public int maxProfit(int[] prices) {
int profit = 0;
for(int i=0;i<prices.length-1;i++){
if(prices[i+1]-prices[i]>0){
profit+=prices[i+1]-prices[i];
}
}
return profit;
}
605. 种花问题
间隔种花
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int cnt = 0;
int len = flowerbed.length;
for(int i=0;i<len&&cnt<n;i++){
if(flowerbed[i]==1)continue;
int pre = i==0?0:flowerbed[i-1]; //首尾特殊处理
int next = i==len-1?0:flowerbed[i+1];
if(pre==0&&next==0){
cnt++;
flowerbed[i] = 1; //种花
}
}
return cnt>=n;
}
763. 划分字母区间
public List<Integer> partitionLabels(String S) {
int[] lastLocation = new int[26]; //记录字符最后出现的位置索引
for(int i=0;i<S.length();i++){
lastLocation[charToindex(S.charAt(i))] = i;
}
int firstIndex = 0;
List<Integer> partitions = new ArrayList<>();
while(firstIndex<S.length()){
int lastIndex = firstIndex;
for(int i=firstIndex;i<S.length()&&i<=lastIndex;i++){
int index = lastLocation[charToindex(S.charAt(i))]; //取出字符的最后出现位置
if(index>lastIndex){
lastIndex=index;
}
}
partitions.add(lastIndex-firstIndex+1);
firstIndex = lastIndex+1;
}
return partitions;
}
private int charToindex(char c){
return c-'a';
}
分治
241.为运算表达式设计优先级
利用函数的意思进行同层面的逻辑组合:运算符 左边可能性 右边可能性 双层遍历添加到当前
public List<Integer> diffWaysToCompute(String expression) {
List<Integer>ways = new ArrayList<Integer>();
for(int i=0;i<expression.length();i++){
char c = expression.charAt(i);
if(c=='+'||c=='-'||c=='*'){
List<Integer> left = diffWaysToCompute(expression.substring(0,i));
List<Integer>right = diffWaysToCompute(expression.substring(i+1));
for(int l:left){
for(int r:right){
switch(c){
case '+':ways.add(l+r);break;
case '-':ways.add(l-r);break;
case '*':ways.add(l*r);break;
}
}
}
}
}
if(ways.size()==0){ //即没有符号
ways.add(Integer.valueOf(expression));
}
return ways;
}
动态规划
斐波那契数列
70.爬楼梯
滚动三元数组的思想
public int climbStairs(int n) {
int pre2 = 0,pre1=1;
for(int i=1;i<=n;i++){
int cnt = pre2 + pre1; //当前台阶i的来源于i-1或者i-2即他们的类别数之和
pre2 = pre1;
pre1 = cnt;
}
return pre1;
}
198.打家劫舍
滚动的三元数组,类似爬楼梯
public int rob(int[] nums) {
int pre2 = 0,pre1=0;
for(int num:nums){
int cur = Math.max(pre2+num,pre1); //获取当前节点处可get的最大值
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
213.打家劫舍 II
环形(首末元素是一致的)
public int rob(int[] nums) {
if(nums.length==0)return 0;
int n = nums.length;
if(n==1)return nums[0];
return Math.max(rob(nums,0,n-2),rob(nums,1,n-1)); //都是n-1的长度
}
public int rob(int[]nums,int first,int last){
int pre2=0,pre1=0;
for(int i=first;i<=last;i++){
int cur = Math.max(pre2+nums[i],pre1);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
矩阵路径
64.最小路径和
public int minPathSum(int[][] grid) {
if(grid.length==0||grid[0].length==0)return 0;
int m = grid.length, n = grid[0].length; //m是纵 n是横
int[] dp = new int[n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
//找出左、上元素较小的dp
//考虑两种特殊情况 i==0 则j位置的上面元素的dp不存在
// j==0 则j位置的前面元素的dp不存在
if(i==0){
dp[j]=j==0?0:dp[j-1];
}else{
dp[j]=j==0?dp[j]:Math.min(dp[j-1],dp[j]);
}
dp[j] += grid[i][j]; //更新i,j位置的dp[j]
}
}
return dp[n-1];
}
62.不同路径
注意有个类似最短编辑距离的初始化
数组区间
413.等差数列划分
dp[i] = dp[i-1] + 1; //多了一个相同的元素目前为a+1个元素 前a个相同的元素等差组合为dp[i-1]则后a个也是dp[i-1] 再者整体a+1也会构成一个
public int numberOfArithmeticSlices(int[] nums) {
if(nums.length==0)return 0;
int[] dp = new int[nums.length];
for(int i=2;i<nums.length;i++){
if(nums[i-1]-nums[i-2]==nums[i]-nums[i-1]){
dp[i] = dp[i-1] + 1;
}
}
int total = 0;
for(int cnt:dp){
total +=cnt;
}
return total;
}
分割整数
343.整数拆分
public int integerBreak(int n) {
int []dp = new int[n+1]; //n+1主要便于逻辑思考 将数与索引一致
dp[1] = 1;
for(int i=2;i<=n;i++){
for(int j =1;j<i;j++){ //从i中拆出j 不断更新dp[i]
dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
279.完全平方数
public int numSquares(int n) {
int[] dp = new int[n + 1]; // 默认初始化值都为0
for (int i = 1; i <= n; i++) {
dp[i] = i; // 最坏的情况就是每次+1
for (int j = 1; i - j * j >= 0; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 动态转移方程
}
}
return dp[n];
}
public int numSquares(int n) {
int[] dp = new int[n+1];
List<Integer> squareList = generateSquareList(n);
for(int i =1;i<=n;i++){
int min = Integer.MAX_VALUE;
for(int square:squareList){
if(square>i){
break;
}
min = Math.min(min,dp[i-square]+1); //自下而上
}
dp[i] =min;
}
return dp[n];
}
//这种产生平方数列的方式还是比较有意思的
private List<Integer> generateSquareList(int n){
int square = 1;
int diff = 3;
List<Integer> list = new ArrayList<>();
while(square<=n){
list.add(square);
square += diff;
diff += 2;
}
return list;
}
91.解码方法
public int numDecodings(String s) {
if(s==null||s.length()==0)return 0;
int n = s.length();
int[] dp = new int[n+1];
dp[0] =1;
dp[1] = s.charAt(0)=='0'?0:1;
for(int i=2;i<=n;i++){
int one = Integer.valueOf(s.substring(i - 1, i));
if(one!=0){
dp[i] = dp[i-1]; //一个字符时,和前面的可能相同
}
if(s.charAt(i-2)=='0')continue; //两个连续字符的前一个不为'0'
int two =Integer.valueOf(s.substring(i-2,i));
if(two<=26){
dp[i] +=dp[i-2]; //这儿时dp[i]+dp[i-2] 而不是dp[i-1]+dp[i-2] 在于后面的字符可能为'0'
}
}
return dp[n];
}
0-1 背包
416. 分割等和子集转换成了sum/2体积的0-1背包问题
public boolean canPartition(int[] nums) {
int sum = computeArraySum(nums);
if(sum%2!=0)return false;
int W = sum/2;
//Arrays.sort(nums); //商品的体积要升序
boolean[] dp = new boolean[W+1];
dp[0] = true; //这个0位置表示背包刚好填满,即找到这个数
for(int num:nums){
for(int j =W;j>=num;j--){ //将 W->1尽可能的去填充 然后策略更新
dp[j] = dp[j] || dp[j-num]; //当j-num为0时为true,就去放
}
}
return dp[W];
}
private int computeArraySum(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
参照背包
public int knapsack(int W, int N, int[] weights, int[] values) {
int[] dp = new int[W + 1];
for (int i = 1; i <= N; i++) {
int w = weights[i - 1], v = values[i - 1];
for (int j = W; j >= 1; j--) {
if (j >= w) {
dp[j] = Math.max(dp[j], dp[j - w] + v); //当dp[j]有物品了, 通过比较价值放还是不放
}
}
}
return dp[W];
}
494. 目标和
public int findTargetSumWays(int[] nums, int S) {
int sum = computeTargetSum(nums);
if(sum<S||(sum+S)%2!=0)return 0;
int W = (sum+S)/2;
int[] dp = new int[W+1];
dp[0] = 1; //这个0位置表示背包刚好填满,即找到这个数 然后加上这个dp即+1
for(int num:nums){
for(int j =W;j>=num;j--){
dp[j] = dp[j] + dp[j-num];
}
}
return dp[W];
}
private int computeTargetSum(int[]nums){
int sum = 0;
for(int num:nums){
sum += num;
}
return sum;
}
139. 单词拆分
完全背包:对物品的迭代是在最里层。即物品没有次数限制 ps与416对比
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
boolean[] dp = new boolean[n+1];
dp[0] = true;
for(int i= 1;i<=n;i++){
for(String word:wordDict){ //物品迭代 在里层
int len = word.length();
if(len<=i&&word.equals(s.substring(i-len,i))){
dp[i] = dp[i] || dp[i-len];
}
}
}
return dp[n];
}
474. 一和零
多维费用的 0-1 背包问题
public int findMaxForm(String[] strs, int m, int n) {
if(strs==null||strs.length==0)return 0;
int[][] dp =new int[m+1][n+1];
for(String str:strs){
int zeros=0,ones=0;
for(char c :str.toCharArray()){
if(c=='0'){
zeros++;
}else{
ones++;
}
}
for(int i=m;i>=zeros;i--){
for(int j=n;j>=ones;j--){
dp[i][j] = Math.max(dp[i][j],dp[i-zeros][j-ones]+1);
}
}
}
return dp[m][n];
}
股票交易
309. 最佳买卖股票时机含冷冻期
参考官方题解
public int maxProfit(int[] prices) {
if(prices==null||prices.length==0)return 0;
int f0 = -prices[0]; //手上持有股票的最大收益
int f1 = 0; //手上不持有股票,并且处于冷冻期中的累计最大收益
int f2 = 0; //手上不持有股票,并且不在冷冻期中的累计最大收益
for(int i = 1;i<prices.length;i++){
int newF0 = Math.max(f0,f2-prices[i]);
int newF1 = f0+prices[i]; //卖
int newF2 = Math.max(f2,f1);
f0 = newF0;
f1 = newF1;
f2 = newF2;
}
return Math.max(f1,f2);
}
714. 买卖股票的最佳时机含手续费
参照309 状态转移
public int maxProfit(int[] prices, int fee) {
if(prices==null||prices.length==0)return 0;
int buy = -prices[0],sell = 0;
for(int i=1;i<prices.length;i++){
//int temp = buy;
buy = Math.max(buy,sell-prices[i]);
sell = Math.max(sell,buy+prices[i]-fee);
}
return sell;
}
123. 买卖股票的最佳时机 III
就是围绕状态转移方程
public int maxProfit(int[] prices) {
int buy1 = -prices[0],sell1 = 0;
int buy2 = -prices[0],sell2 = 0;
for(int i=1;i<prices.length;i++){
buy1 = Math.max(buy1,-prices[i]);
sell1 = Math.max(sell1,buy1+prices[i]);
buy2 = Math.max(buy2,sell1-prices[i]);
sell2 = Math.max(sell2,buy2+prices[i]);
}
return sell2;
}
188. 买卖股票的最佳时机 IV
参考官方解释
public int maxProfit(int k, int[] prices) {
if(prices==null||prices.length==0)return 0;
int n = prices.length;
k = Math.min(k,n/2); //买,卖是两天的事
int[] buy = new int[k+1];
int[] sell = new int[k+1];
buy[0] = -prices[0];
sell[0] = 0;
for(int i=1;i<=k;i++){
buy[i] = sell[i] = Integer.MIN_VALUE / 2;
}
for(int i=1;i<n;i++){
buy[0] = Math.max(buy[0],sell[0]-prices[i]);
for(int j=1;j<=k;j++){
//两种可能 i-1天就进行了j笔交易持有股 或不持有股(得买
buy[j] = Math.max(buy[j],sell[j]-prices[i]);
//两种可能 i-1天就进行了j笔交易 i-1天进行了j-1笔交易还持有股(得卖
sell[j] = Math.max(sell[j],buy[j-1]+prices[i]);
}
}
return Arrays.stream(sell).max().getAsInt(); //不是交易越多越好所以整体取最大
}
子序列
300.最长递增子序列
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
for(int i =0;i<nums.length;i++){
int max = 1;
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){ //由于这个条件,所以最后一个dp并不是最大
max = Math.max(max,dp[j]+1);
}
}
dp[i] =max; //将每个位置的记录下
}
int ret = 0;
for (int i = 0; i < n; i++) {
ret = Math.max(ret, dp[i]);
}
return ret;
}
646.最长数对链
扎气球问题
376. 摆动序列
public int wiggleMaxLength(int[] nums) {
if(nums==null||nums.length==0)return 0;
int up =1,down =1;
for(int i=1;i<nums.length;i++){
if(nums[i]>nums[i-1]){
up = down + 1; //这样写其实是在累积 题目说可以人为删除不是的
}
if(nums[i]<nums[i-1]){
down = up+1;
}
}
return Math.max(up,down);
}
1143.最长公共子序列
public int longestCommonSubsequence(String text1, String text2) {
if(text1.length()==0||text2.length()==0)return 0;
int n1 = text1.length(), n2 = text2.length();
int[][] dp = new int[n1+1][n2+1];
for(int i=1;i<=n1;i++){
for(int j=1;j<=n2;j++){
if(text1.charAt(i-1)==text2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+1;
}else{
dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
}
}
}
return dp[n1][n2];
}
字符串编辑
- 两个字符串的删除操作
最长公共子字符串的变种
72. 编辑距离
最长公共字符串的变种 PS:船名字符识别项目就是拿编辑距离度量的
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m+1][n+1];
for(int i=1;i<=m;i++){
dp[i][0] = i;
}
for(int i=1;i<=n;i++){
dp[0][i] = i;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
}
return dp[m][n];
}
10. 正则表达式匹配
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][]dp = new boolean[m+1][n+1];
dp[0][0] =true;
for(int i=0;i<=m;i++){
for(int j=1;j<=n;j++){
if(p.charAt(j-1)=='*'){ //判断第j个字符
dp[i][j] = dp[i][j-2]; //先默认匹配空
if(matches(s,p,i,j-1)){ //得看*的前一个字符是否匹配
dp[i][j] = dp[i][j]||dp[i-1][j]; // dp[i-1][j]指*匹配多个字符
}
}else{
if(matches(s,p,i,j)){
dp[i][j] = dp[i-1][j-1]; //
}
}
}
}
return dp[m][n];
}
private boolean matches(String s,String p,int i,int j){
if(i==0||j==0){
return false;
}
if(p.charAt(j-1)=='.'){
return true;
}
return p.charAt(j-1)==s.charAt(i-1);
}
44. 通配符匹配
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 1; i <= n; ++i) {
if (p.charAt(i - 1) == '*') {
dp[0][i] = true;
} else {
break;
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
} else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/wildcard-matching/solution/tong-pei-fu-pi-pei-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
650. 只有两个键的键盘(复制粘贴字符)
public int minSteps(int n) {
if(n==1)return 0;
for(int i=2;i<=Math.sqrt(n);i++){
if(n%i==0)return i+minSteps(n/i); //分堆
}
return n;
}
数学
204. 计数质数
埃氏筛:参考官方解读
public int countPrimes(int n) {
boolean[] notPrimes = new boolean[n+1];
int cnt =0;
for(int i=2;i<n;i++){
if(notPrimes[i]){
continue;
}
cnt ++;
for(long j=(long)(i)*i;j<n;j+=i){
notPrimes[(int)j] = true;
}
}
return cnt;
}
传统写法
public int countPrimes(int n) {
int cnt =0;
for(int i=2;i<n;i++){
cnt += isPrime(i)?1:0;
}
return cnt;
}
private boolean isPrime(int x){
for(int i =2;i*i<=x;i++){
if(x%i==0){
return false;
}
}
return true;
}
172. 阶乘后的零
这里是阶乘贡献5
思路:先整体从含有5的数中各抽取一个5即n/5 可能有的数不止一个5 即n/5>=5 在对不止一个5的数再抽取一个 (n/5)/5 一次进行下去
同理:统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可
//非递归方式
public int trailingZeroes(int n) {
int zeroCnt = 0;
while(n>0){
n /=5;
zeroCnt += n ;
}
return zeroCnt;
}
//递归方式
public int trailingZeroes(int n) {
return n==0?0:n/5+ trailingZeroes(n/5);
}
public String toHex(int num) {
if(num==0)return "0";
char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuilder strBuild = new StringBuilder();
while(num!=0){
strBuild.append(map[num&0b1111]);
num >>>= 4;
}
return strBuild.reverse().toString();
}
Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。
public String convertToBase16(int num) {
return Integer.toString(num, 16);
}
415. 字符串相加
public String addStrings(String num1, String num2) {
int n1 = num1.length()-1, n2 = num2.length()-1;
int carry = 0;
StringBuilder strBuild = new StringBuilder();
while(carry!=0||n1>=0||n2>=0){
if(n1>=0){
carry+=num1.charAt(n1--)-'0'; //低位为字符串长度-1索引的字符
}
if(n2>=0){
carry+=num2.charAt(n2--)-'0';
}
strBuild.append(carry%10);
carry/=10;
}
return strBuild.reverse().toString();
}
462. 最少移动次数使数组元素相等 II
相遇问题:移动距离最小的方式是所有元素都移动到中位数
public int minMoves2(int[] nums) {
Arrays.sort(nums);
int l =0, h=nums.length - 1;
int moves = 0;
while(l<h){
moves += nums[h] - nums[l];
l++;
h--;
}
return moves;
}
优化:快速选择
238. 除自身以外数组的乘积
public int[] productExceptSelf(int[] nums) {
//主要是为了构建上下三角矩阵 当前元素位置为1
int n = nums.length;
int[] products = new int[n];
Arrays.fill(products,1);
//下三角
int left = 1;
for(int i =1;i<n;i++){ //对于下三角累积i = 0不用考虑
left *= nums[i-1]; //累积元素,不能累积自己 向左错开一位
products[i] *= left;
}
//上三角
int right = 1;
for(int i=n-2;i>=0;i--){ //对于上三角累积i=n-1不用考虑
right *= nums[i+1]; //累积元素,不能累积自己 向右错开一位
products[i] *=right;
}
return products;
}
排序
215. 数组中的第K个最大元素
参考link
切分
import java.util.Random;
class Solution {
private static Random random = new Random(System.currentTimeMillis());
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
// 转换一下,第 k 大元素的索引是 len - k
int target = len - k;
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
/**
* 在数组 nums 的子区间 [left, right] 执行 partition 操作,返回 nums[left] 排序以后应该在的位置
* 在遍历过程中保持循环不变量的语义
* 1、[left + 1, j] < nums[left]
* 2、(j, i] >= nums[left]
*
* @param nums
* @param left
* @param right
* @return
*/
public int partition(int[] nums, int left, int right) {
// 在区间随机选择一个元素作为标定点
if (right > left) {
int randomIndex = left + 1 + random.nextInt(right - left);
swap(nums, left, randomIndex);
}
int pivot = nums[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (nums[i] < pivot) {
// 小于 pivot 的元素都被交换到前面
j++;
swap(nums, j, i);
}
}
// 在之前遍历的过程中,满足 [left + 1, j] < pivot,并且 (j, i] >= pivot
swap(nums, j, left); //此时j位置的值比pivot小
// 交换以后 [left, j - 1] < pivot, nums[j] = pivot, [j + 1, right] >= pivot
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
堆排
import java.util.PriorityQueue;
public class Solution {
// 根据 k 的不同,选最大堆和最小堆,目的是让堆中的元素更小
// 思路 1:k 要是更靠近 0 的话,此时 k 是一个较小的数,用最大堆
// 例如在一个有 6 个元素的数组里找第 5 大的元素
// 思路 2:k 要是更靠近 len 的话,用最小堆
// 所以分界点就是 k = len - k
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
if (k <= len - k) {
// System.out.println("使用最小堆");
// 特例:k = 1,用容量为 k 的最小堆
// 使用一个含有 k 个元素的最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, (a, b) -> a - b);
for (int i = 0; i < k; i++) {
minHeap.add(nums[i]);
}
for (int i = k; i < len; i++) {
// 看一眼,不拿出,因为有可能没有必要替换
Integer topEle = minHeap.peek();
// 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去
if (nums[i] > topEle) {
minHeap.poll();
minHeap.add(nums[i]);
}
}
return minHeap.peek();
} else {
// System.out.println("使用最大堆");
assert k > len - k;
// 特例:k = 100,用容量为 len - k + 1 的最大堆
int capacity = len - k + 1;
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(capacity, (a, b) -> b - a);
for (int i = 0; i < capacity; i++) {
maxHeap.add(nums[i]);
}
for (int i = capacity; i < len; i++) {
// 看一眼,不拿出,因为有可能没有必要替换
Integer topEle = maxHeap.peek();
// 只要当前遍历的元素比堆顶元素小,堆顶弹出,遍历的元素进去
if (nums[i] < topEle) {
maxHeap.poll();
maxHeap.add(nums[i]);
}
}
return maxHeap.peek();
}
}
}
347. 前 K 个高频元素
桶排序(推荐)
public int[] topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap<>();
for(int num:nums){
if (map.containsKey(num)) {
map.put(num,map.get(num)+1);
}else{
map.put(num,1);
}
}
//桶排序
//将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标
List<Integer>[] list = new ArrayList[nums.length+1];
for(int key:map.keySet()){
int cnt = map.get(key);
if(list[cnt]==null){
list[cnt] = new ArrayList(); //用于存储出现次数相同的多个数字
}
list[cnt].add(key); //将数字给加进来
}
List<Integer> res = new ArrayList();
// 倒序遍历数组获取出现次数从大到小的排列
for(int i=list.length-1;i>=0&&res.size()<k;i--){
if(list[i]==null)continue;
res.addAll(list[i]);
}
return res.stream().mapToInt(i->i).toArray(); //将List<Integer>转为int[]
}
堆排序
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res.stream().mapToInt(i->i).toArray(); //将List<Integer>转为int[]
}
912.快速排序递归与非递归实现
import java.util.Random;
public class Solution {
// 快速排序 2:双指针(指针对撞)快速排序
/**
* 列表大小等于或小于该大小,将优先于 quickSort 使用插入排序
*/
private static final int INSERTION_SORT_THRESHOLD = 7;
private static final Random RANDOM = new Random();
public int[] sortArray(int[] nums) {
int len = nums.length;
quickSort(nums, 0, len - 1);
return nums;
}
private void quickSort(int[] nums, int left, int right) {
// 小区间使用插入排序
if (right - left <= INSERTION_SORT_THRESHOLD) {
insertionSort(nums, left, right);
return;
}
int pIndex = partition(nums, left, right);
quickSort(nums, left, pIndex - 1);
quickSort(nums, pIndex + 1, right);
}
/**
* 对数组 nums 的子区间 [left, right] 使用插入排序
*
* @param nums 给定数组
* @param left 左边界,能取到
* @param right 右边界,能取到
*/
private void insertionSort(int[] nums, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int temp = nums[i];
int j = i;
while (j > left && nums[j - 1] > temp) {
nums[j] = nums[j - 1];
j--;
}
nums[j] = temp;
}
}
private int partition(int[] nums, int left, int right) {
int randomIndex = left + RANDOM.nextInt(right - left + 1);
swap(nums, randomIndex, left);
int pivot = nums[left];
int lt = left + 1;
int gt = right;
// 循环不变量:
// all in [left + 1, lt) <= pivot
// all in (gt, right] >= pivot
while (true) {
while (lt <= right && nums[lt] < pivot) {
lt++;
}
while (gt > left && nums[gt] > pivot) {
gt--;
}
if (lt >= gt) {
break;
}
// 细节:相等的元素通过交换,等概率分到数组的两边
swap(nums, lt, gt);
lt++;
gt--;
}
swap(nums, left, gt);
return gt;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
非递归
private void quickSort(int[] nums) {
LinkedList<Integer>stack = new LinkedList<>();
stack.push(0);
stack.push(nums.length-1);
while(!stack.isEmpty()){
int right = stack.pop();
int left = stack.pop();
if(left<right){
int pIndex = partition(nums, left, right); //排序的核心换种方式鞭策
if(left<pIndex){
stack.push(left);
stack.push(pIndex-1);
}
if(right>pIndex){
stack.push(pIndex+1);
stack.push(right);
}
}
}
}
75. 颜色分类
三向切分
public void sortColors(int[] nums) {
int len = nums.length;
if (len < 2) {
return;
}
// all in [0, zero] = 0
// all in (zero, i) = 1
// all in (two, len - 1] = 2
// 为了保证初始化的时候 [0, zero] 为空,设置 zero = -1,
// 所以下面遍历到 0 的时候,先加,再交换
int zero = -1;
// 为了保证初始化的时候 (two, len - 1] 为空,设置 two = len - 1
// 所以下面遍历到 2 的时候,先交换,再减
int two = len - 1;
int i = 0;
// 当 i == two 的时候,还有一个元素还没有看,
// 因此,循环可以继续的条件是 i <= two
while (i <= two) {
if (nums[i] == 0) {
zero++;
swap(nums, i, zero);
i++;
} else if (nums[i] == 1) {
i++;
} else {
swap(nums, i, two);
two--;
}
}
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
二分查找
int mid = l+(h-l)/2;
int sqrt = x/mid;
查找target最先出现的位置
public int binarySearch(int[] nums, int key) {
int l = 0, h = nums.length - 1;
while (l < h) {
int m = l + (h - l) / 2;
if (nums[m] >= key) {
h = m;
} else {
l = m + 1;
}
}
return l;
}
69. x 的平方根
public int mySqrt(int x) {
if(x<=1)return x;
int l = 1, h = x;
while(l<=h){
int mid = l+(h-l)/2;
int sqrt = x/mid; //sqrt*sqrt = x;
if(mid==sqrt){ //只有x是整数的平方 才在这里返回
return mid;
}else if(mid>sqrt){
h = mid -1;
}else{
l = mid +1;
}
}
return h; //取出小数的整数部分返回
}
744. 寻找比目标字母大的最小字母
public char nextGreatestLetter(char[] letters, char target) {
int n = letters.length;
int l = 0, h = n-1;
while(l<=h){
int mid = l + (h-l)/2;
if(letters[mid]<=target){
//有两种情况
//1.letters[mid] = target 则letters[mid+1]恰好>target 之后就是不断的更新h
//2.letters[mid] < target可能letters[mid+1]恰好>target(之后就是不断更新h)
// 也可能不是这么回事儿(之后还是得找能使letters[mid+1]恰好>target的mid),最终可能因为找不到l超出长度
l = mid +1;
}else{
h = mid - 1;
}
}
return l<n?letters[l]:letters[0];
}
34. 在排序数组中查找元素的第一个和最后一个位置
结合744. 寻找比目标字母大的最小字母 找出last再-1
public int[] searchRange(int[] nums, int target) {
int first = binarySearch(nums, target);
int last = binarySearch(nums, target + 1) - 1;
if (first == nums.length || nums[first] != target) {
return new int[]{-1, -1};
} else {
return new int[]{first, Math.max(first, last)};
}
}
//找出在最先出现target的索引 没找到返回总长度
private int binarySearch(int[] nums, int target) {
int l = 0, h = nums.length; // 正常是nums.length-1
//有这么种情况,target+1不存在,即数组中值一只小,则l就等于h跳出循环
while (l < h) {
int m = l + (h - l) / 2;
if (nums[m] >= target) {
h = m;
} else {
l = m + 1;
}
}
return l;
}
540. 有序数组中的单一元素
public int singleNonDuplicate(int[] nums) {
int l = 0,h = nums.length-1;
while(l<h){ // h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式
int m = l+(h-l)/2;
if(m%2==1){
m --;
}
if(nums[m]==nums[m+1]){ //<=m+1没有
l = m+2; //m+2存在嫌疑
}else{
h = m;
}
}
return nums[l];
}
153. 寻找旋转排序数组中的最小值
public int findMin(int[] nums) {
int l = 0, h=nums.length-1;
while(l<h){
int m = l+(h-l)/2;
if(nums[m]<=nums[h]){ //说明这一截没问题
h = m;
}else{
l = m+1;
}
}
return nums[l];
}
4. 寻找两个正序数组的中位数
分割线 人为规定中位数在分割线左边
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1.length>nums2.length){
int[] temp = nums2;
nums2 = nums1;
nums1 = temp;
}
int m = nums1.length;
int n = nums2.length;
int totalLeft = (m+n+1)/2;
int left = 0;
int right = m;
while(left<right){ //二分查找
int i = left+ (right-left+1)/2;
int j = totalLeft - i;
if(nums1[i-1]<=nums2[j]){ //找到最右边的那个满足条件的
left = i;
}else{
right = i-1;
}
}
int i = left;
int j = totalLeft - i;
int leftNums1Max = i==0?Integer.MIN_VALUE:nums1[i-1];
int leftNums2Max = j==0?Integer.MIN_VALUE:nums2[j-1];
int rightNums1Min = i==m?Integer.MAX_VALUE:nums1[i];
int rightNums2Min = j==n?Integer.MAX_VALUE:nums2[j];
if((m+n)%2==1){//奇数
return Math.max(leftNums1Max,leftNums2Max);
}else{
return (double)(Math.max(leftNums1Max,leftNums2Max)+Math.min(rightNums1Min,rightNums2Min))/2;
}
}
搜索
BFS
使用 BFS 只能求解无权图的最短路径。
在程序实现 BFS 时需要考虑以下问题:
队列:用来存储每一轮遍历得到的节点;
标记:对于遍历过的节点,应该将它标记,防止重复遍历
127. 单词接龙
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
Set<String> wordSet = new HashSet<>(wordList);
if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
return 0;
}
wordSet.remove(beginWord);
// 第 2 步:图的广度优先遍历,必须使用队列和表示是否访问过的 visited 哈希表
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
Set<String> visited = new HashSet<>();
visited.add(beginWord);
// 第 3 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1
int step = 1;
while (!queue.isEmpty()) {
int currentSize = queue.size();
for (int i = 0; i < currentSize; i++) {
// 依次遍历当前队列中的单词
String currentWord = queue.poll();
// 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1
if (changeWordEveryOneLetter(currentWord, endWord, queue, visited, wordSet)) {
return step + 1;
}
}
step++;
}
return 0;
}
/**
* 尝试对 currentWord 修改每一个字符,看看是不是能与 endWord 匹配
*
* @param currentWord
* @param endWord
* @param queue
* @param visited
* @param wordSet
* @return
*/
private boolean changeWordEveryOneLetter(String currentWord, String endWord,
Queue<String> queue, Set<String> visited, Set<String> wordSet) {
char[] charArray = currentWord.toCharArray();
for (int i = 0; i < endWord.length(); i++) {
// 先保存,然后恢复
char originChar = charArray[i];
for (char k = 'a'; k <= 'z'; k++) {
if (k == originChar) {
continue;
}
charArray[i] = k;
String nextWord = String.valueOf(charArray);
if (wordSet.contains(nextWord)) {
if (nextWord.equals(endWord)) {
return true;
}
if (!visited.contains(nextWord)) {
queue.add(nextWord);
// 注意:添加到队列以后,必须马上标记为已经访问
visited.add(nextWord);
}
}
}
// 恢复
charArray[i] = originChar;
}
return false;
}
DFS
DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
695. 岛屿的最大面积
沉岛思想
public int maxAreaOfIsland(int[][] grid) {
if(grid==null||grid[0]==null) return 0;
int maxArea = 0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
maxArea = Math.max(maxArea,dfs(i,j,grid));
}
}
return maxArea;
}
private int dfs(int i,int j,int[][] grid){ //返回当前岛面积
if(i<0||j<0||i>grid.length-1||j>grid[0].length-1||grid[i][j]==0){
return 0;
}
grid[i][j] = 0; //湮灭掉 要不就重复了
int area = 1;
//环顾四周的面积
area +=dfs(i+1,j,grid); //上
area +=dfs(i-1,j,grid); //下
area +=dfs(i,j-1,grid); //左
area +=dfs(i,j+1,grid); //右
return area;
}
200. 岛屿数量
public int numIslands(char[][] grid) {
if(grid==null||grid[0]==null){
return 0;
}
int isLandNums = 0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1'){
isLandNums ++;
dfs(grid,i,j); //出现一个'1'将这一片相连的都置为0了
}
}
}
return isLandNums;
}
private void dfs(char[][]grid,int nr,int nc){
if(nc<0||nr<0||nr>grid.length-1||nc>grid[0].length-1||grid[nr][nc]=='0'){
return;
}
grid[nr][nc] = '0'; //沉没掉
dfs(grid,nr+1,nc);
dfs(grid,nr-1,nc);
dfs(grid,nr,nc+1);
dfs(grid,nr,nc-1);
}
463. 岛屿的周长
DFS 寻找天涯海角 不是啥时小岛都可以沉没
public int islandPerimeter(int[][] grid) {
if(grid==null||grid[0]==null){
return 0;
}
int ans = 0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]==1){
ans +=dfs(grid,i,j); //出现一个1将这一片相连的都置为2了,不能置为0
}
}
}
return ans;
}
private int dfs(int[][] grid,int r,int c){
if(r<0||c<0||r>grid.length-1||c>grid[0].length-1||grid[r][c]==0){
return 1; //在岛上碰壁 遇到区域边缘或者海水
}
if(grid[r][c]==2){ //遇到2是标记过的陆地
return 0;
}
grid[r][c] = 2; //不能乱沉没了
int res = 0;
//看看四周是不是天涯海角
res += dfs(grid,r+1,c);
res += dfs(grid,r-1,c);
res += dfs(grid,r,c+1);
res += dfs(grid,r,c-1);
return res;
}
130. 被围绕的区域
这题和463有点像 设计到岛屿与边界相连
public void solve(char[][] board) {
if(board==null||board[0]==null)return;
int nr = board.length, nc = board[0].length;
//找出连接四周的小岛,并标记
for(int i=0;i<nr;i++){
dfs(board,i,0);
dfs(board,i,nc-1);
}
for(int i=0;i<nc;i++){
dfs(board,0,i);
dfs(board,nr-1,i);
}
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(board[i][j]=='O'){ //更改
board[i][j] = 'X';
}
if(board[i][j]=='A'){
board[i][j] = 'O'; //恢复
}
}
}
}
private void dfs(char[][]board,int r,int c){
if(r<0||c<0||r>=board.length||c>=board[0].length||board[r][c]!='O'){
return;
}
board[r][c] = 'A'; //与边界相连的小岛给个独特标志
dfs(board,r+1,c);
dfs(board,r-1,c);
dfs(board,r,c+1);
dfs(board,r,c-1);
}
547. 省份数量
public int findCircleNum(int[][] isConnected) {
if(isConnected==null||isConnected[0]==null)return 0;
int n = isConnected.length;
boolean[] visited = new boolean[n];
int count = 0;
for(int i=0;i<n;i++){
if(!visited[i]){ //类似于海岛个数 只不过这个用了boolean[]来标记遍历过位置
dfs(isConnected,i,visited);
count ++;
}
}
return count;
}
private void dfs(int[][]isConnected,int i,boolean[] visited){
visited[i] = true; //记录一下遍历过了
for(int j=0;j<isConnected.length;j++){ //找与i有关联且没有遍历过的j
if(isConnected[i][j]==1&&!visited[j]){
dfs(isConnected,j,visited);
}
}
}
417. 太平洋大西洋水流问题
浪潮思想 可达交汇
//由于当前得与后一个值比较所以得用方向数组
private int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
if(heights==null||heights[0]==null){
return null;
}
int nr = heights.length;
int nc = heights[0].length;
//可达记录
boolean[][] reachP = new boolean[nr][nc];
boolean[][] reachA = new boolean[nr][nc];
//浪潮思想 太平洋与大西洋分别义两个边为起点 类似题130
for(int i=0;i<nr;i++){
dfs(heights,i,0,reachP);
dfs(heights,i,nc-1,reachA);
}
for(int i=0;i<nc;i++){
dfs(heights,0,i,reachP);
dfs(heights,nr-1,i,reachA);
}
List<List<Integer>> lists = new ArrayList<>();
List<Integer> list = null;
for(int i=0;i<nr;i++){
for(int j=0;j<nc;j++){
//寻找交集 即都可达的陆地
if(reachP[i][j]&&reachA[i][j]){
list = new ArrayList<>();
list.add(i);
list.add(j);
lists.add(list);
}
}
}
return lists;
}
private void dfs(int[][]heights,int r,int c,boolean[][] reach){
if(reach[r][c])return;
reach[r][c] = true; //标记下
for(int[]direction:directions){
int nextR = r + direction[0];
int nextC = c +direction[1];
if(nextR<0||nextC<0||nextR>=heights.length||nextC>=heights[0].length||heights[nextR][nextC]<heights[r][c]){
continue;
}
dfs(heights,nextR,nextC,reach);
}
}
Backtracking
Backtracking(回溯)属于 DFS。
普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
而 Backtracking 主要用于求解 排列组合 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即就返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
17. 电话号码的字母组合
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<>();
if(digits.length()==0)return combinations;
Map<Character,String> map = new HashMap<>(){{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
backTracking(combinations,new StringBuilder(),0,digits,map);
return combinations;
}
private void backTracking(List<String> combinations,StringBuilder combination,int index,String digits,Map<Character,String> map){
if(combination.length()==digits.length()){
combinations.add(combination.toString()); //找到便回退
}else{
String letters = map.get(digits.charAt(index));
for(int i=0;i<letters.length();i++){
combination.append(letters.charAt(i)); //添加
backTracking(combinations,combination,index+1,digits,map); //这个index+1去下个号码
combination.deleteCharAt(index); //回退后删除 让当前级别的其它字符填充
}
}
}
79. 单词搜索
private int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};
public boolean exist(char[][] board, String word) {
if(word==null)return true;
if(board==null||board[0]==null)return false;
int nr = board.length,nc = board[0].length;
boolean[][] visited = new boolean[nr][nc];
for(int i=0;i<nr;i++){
for(int j=0;j<nc;j++){
if(backTracking(board,word,0,i,j,visited)){
return true;
}
}
}
return false;
}
private boolean backTracking(char[][] board,String word,int len,int r,int c,boolean[][] visited){
if(len==word.length()){
return true;
}
if(r<0||c<0||r>=board.length||c>=board[0].length||word.charAt(len)!=board[r][c]||visited[r][c]){
return false;
}
visited[r][c] = true; //限制当前的递归链不可重复调用该
for(int[]direction:directions){
int nextR = r + direction[0];
int nextC = c + direction[1];
if(backTracking(board,word,len+1,nextR,nextC,visited)){
return true;
}
}
visited[r][c] = false; //回退 取消限制
return false;
}
257. 二叉树的所有路径
public List<String> binaryTreePaths(TreeNode root) {
List<String> list= new ArrayList<>();
backTracking(root,list,"");
return list;
}
public void backTracking(TreeNode node,List<String> list,String path){
if(node!=null){
StringBuilder pathSB = new StringBuilder(path);
pathSB.append(Integer.toString(node.val));
if(node.left==null&&node.right==null){
list.add(pathSB.toString());
}else{
pathSB.append("->");
backTracking(node.left,list,pathSB.toString());
backTracking(node.right,list,pathSB.toString());
}
}
}
排列回溯
列表中元素的位置不同,这两个就是不同的结果
46. 全排列
不同元素的
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
boolean[] visited = new boolean[nums.length];
List<Integer>list = new ArrayList<>();
backTracking(nums,lists,list,visited);
return lists;
}
private void backTracking(int[]nums,List<List<Integer>> lists,List<Integer> list,boolean[] visited){
if(list.size()==nums.length){
lists.add(new ArrayList<>(list));
}
for(int i=0;i<nums.length;i++){
if(visited[i])continue;
visited[i] = true; //添加
list.add(nums[i]);
backTracking(nums,lists,list,visited);
list.remove(list.size()-1); //当前位置用别的元素填充
visited[i] = false; //删除 当前元素可填充到别的位置
}
}
47. 全排列 II
有重复元素的
对比题46
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
boolean[] visited = new boolean[nums.length];
List<Integer>list = new ArrayList<>();
Arrays.sort(nums); //不同点1
backTracking(nums,lists,list,visited);
return lists;
}
private void backTracking(int[]nums,List<List<Integer>> lists,List<Integer> list,boolean[] visited){
if(list.size()==nums.length){
lists.add(new ArrayList<>(list));
}
for(int i=0;i<nums.length;i++){
if(visited[i])continue;
//前一个相同元素是使用了然后被回退取消了,这下不能再放个相同的元素 要不就重复了
if(i>0&&nums[i]==nums[i-1]&&!visited[i-1]){ //不同点2
continue;
}
visited[i] = true; //添加
list.add(nums[i]);
backTracking(nums,lists,list,visited);
list.remove(list.size()-1); //当前位置用别的元素填充
visited[i] = false; //删除 当前元素可填充到别的位置
}
}
组合回溯
77. 组合
这题是组合问题 不是排列问题 所以和元素顺序没有关系
组合与排列的唯一区别:for循环有起始点start 即for(int i=start;i<=n;i++)
没有visited访问记录数组 这样做的目的前面的元素得先使用(可连续使用),使用过了不能再在后面使用
原始函数:backTracking(List<List<Integer>> lists,List<Integer> list,int start,int n,int k)内部调用
backTracking(lists,list,i+1,n,k); //这里的i+1起始限制 之后的数只能是后面位置的数
backTracking(lists,list,i,n,k); //这里的i起始限制 之后的数可能是当前位置(即可连续使用)或后面位置的数
回溯+剪支
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backTracking(lists,list,1,n,k);
return lists;
}
private void backTracking(List<List<Integer>> lists,List<Integer> list,int start,int n,int k){
if(list.size()==k){
lists.add(new ArrayList<>(list));
return;
}
//for(int i=start;i<=n;i++){
//优化:分析搜索起点的上界进行剪枝 //起点太靠后,元素根本不够,直接不用搜
for(int i=start;i<=n-(k-list.size())+1;i++){
list.add(i);
//修改start进行剪支
backTracking(lists,list,i+1,n,k); //这里的i+1限制 之后的数只能是后面位置的数
list.remove(list.size()-1);
}
}
39. 组合总和
结合77 唯一不同本题当前元素可以连续用多次
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> list = new ArrayList<>();
backTracking(lists,list,candidates,target,0);
return lists;
}
private void backTracking(List<List<Integer>> lists,List<Integer> list,int[] candidates, int target,int begin){
if(target==0){
lists.add(new ArrayList<>(list));
return;
}
if(target<0)return;
for(int i=begin;i<candidates.length;i++){
list.add(candidates[i]);
backTracking(lists,list,candidates,target-candidates[i],i); //这里的i限制之后的数只能是当前或者后面位置的数
list.remove(list.size()-1);
}
}
}
40. 组合总和 II
结合全排列的 题47
不同元素的组合问题 得用visited 目的在于看前一位置的相同值元素是否访问回退为false了,若是的话当前这个相同值元素跳过,要不组合结果就重复了。若仍然为true这时可以添加上去即表现为连续的字符不过来自于不同位置
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> list = new ArrayList<>();
Arrays.sort(candidates); //有重复得排序
backTracking(lists,list,new boolean[candidates.length],candidates,target,0);
return lists;
}
private void backTracking(List<List<Integer>> lists,List<Integer> list,boolean[] visited,int[] candidates, int target,int begin){
if(target==0){
lists.add(new ArrayList<>(list));
return;
}
if(target<0)return;
for(int i=begin;i<candidates.length;i++){
if(i!=0&&candidates[i]==candidates[i-1]&&!visited[i-1])continue;//前一个相同元素访问过了,且已经被回退 如果没有被回退还是可以访问的
visited[i] = true;
list.add(candidates[i]);
backTracking(lists,list,visited,candidates,target-candidates[i],i+1); //这里的i+1限制之后的数只能是后面位置的数
list.remove(list.size()-1);
visited[i] = false;
}
}
}
216. 组合总和 III
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> lists = new ArrayList<>();
backTracking(lists,new ArrayList<>(),n,1,k);
return lists;
}
private void backTracking(List<List<Integer>> lists,List<Integer> list, int target,int begin,int k){
if(list.size()==k&&target==0){
lists.add(new ArrayList<>(list));
return;
}
if(list.size()==k||target==0)return; //要么数个数到了,可能没达到目标, 要么是达到目标了
for(int i=begin;i<=9;i++){
list.add(i);
backTracking(lists,list,target-i,i+1,k); //这里的i限制之后的数只能是后面位置的数
list.remove(list.size()-1);
}
}
字符串回溯分割
93. 复原 IP 地址
public List<String> restoreIpAddresses(String s) {
List<String> addresses = new ArrayList<>();
if(s.length()<4)return addresses;
StringBuilder address = new StringBuilder();
backTracking(addresses,address,0,s);
return addresses;
}
public void backTracking(List<String> addresses,StringBuilder address,int k,String s){
if(k==4||s.length()==0){ // k表示有几段
if(k==4&&s.length()==0){
addresses.add(address.toString()); //
}
}else{
for(int i=0;i<s.length()&&i<=2;i++){
if(i!=0&&s.charAt(0)=='0')break; //不能以'0'开头分割 单独一个0还是可以的
String part = s.substring(0,i+1); //截取前0~i个字符
if(Integer.valueOf(part)<=255){
part = (k!=0?".":"") + part;
address.append(part); //添加
backTracking(addresses,address,k+1,s.substring(i+1));
address.delete(address.length()-part.length(),address.length()); //删除
}
}
}
}
131. 分割回文串
该题目是将整个字符串分割成 完全由回文字符串组成的list
与93复原IP地址的分割处理方式很像
public List<List<String>> partition(String s) {
List<List<String>>lists = new ArrayList<>();
backTracking(s,lists,new ArrayList<>());
return lists;
}
private void backTracking(String s,List<List<String>>lists,List<String>list){
if(s.length()==0){ //字符都被放置完了
lists.add(new ArrayList<>(list));
return;
}
for(int i=0;i<s.length();i++){
String subStr = s.substring(0,i+1); //截取前多少个字符
if(isPalindrome(subStr,0,i)){ //保证每一子段都是回文才可以继续
list.add(subStr);
backTracking(s.substring(i+1),lists,list);
list.remove(list.size()-1);
}
}
}
private boolean isPalindrome(String subStr,int start,int end){
while(start<end){
if(subStr.charAt(start++)!=subStr.charAt(end--)){
return false;
}
}
return true;
}
棋谱填空
这种问题在于约束的构建
37. 解数独
class Solution {
private boolean[][] lines = new boolean[9][9];
private boolean[][] columns = new boolean[9][9];
private boolean[][][] block = new boolean[3][3][9];
private List<int[]> spaces = new ArrayList<>();
private boolean valid = false; //标注有没有完成
public void solveSudoku(char[][] board) {
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(board[i][j]=='.'){
spaces.add(new int[]{i,j});
}else{
int digit = board[i][j] - '0';
lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=true;
//表示第i行 第j列 第[i/3][j/3]个九宫格 放置了数字digit
}
}
}
dfs(board,0);
}
private void dfs(char[][]board,int pos){
if(pos==spaces.size()){
valid = true;
return;
}
int[] space = spaces.get(pos);
int i = space[0], j = space[1];
for(int digit=1;digit<=9&&!valid;digit++){ //看每个空白 是否能放下某个数
if(!lines[i][digit-1]&&!columns[j][digit-1]&&!block[i/3][j/3][digit-1]){
//都为false
board[i][j] = (char)(digit+'0');
lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=true;
dfs(board,pos+1);
lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=false;
}
}
}
}
51. N 皇后
和题37解数独一个想法 记录相关不可放的位置
class Solution {
private char[][] nQueens; //棋盘
private boolean[] diagonals45Used; //记录45°主对角线
private boolean[] diagonals135Used;
private boolean[] colums;
private int n;
public List<List<String>> solveNQueens(int n) {
List<List<String>>lists = new ArrayList<>();
this.n = n;
nQueens = new char[n][n];
for(int i=0;i<nQueens.length;i++){
Arrays.fill(nQueens[i],'.');
}
diagonals45Used = new boolean[2*n-1]; //最多一行带一列,去掉一公共的
diagonals135Used = new boolean[2*n-1];
colums = new boolean[n];
backTracking(lists,0);
return lists;
}
private void backTracking(List<List<String>>lists,int r){
if(r==n){
List<String> list = new ArrayList<>();
for(char[]rows:nQueens){
list.add(new String(rows));
}
lists.add(list);
return;
}
for(int c=0;c<n;c++){
int index45Used = r + c; //45°线上该值相等
int index135Used = n - 1 -(r-c); //135°线上该值相等
if(diagonals45Used[index45Used]||diagonals135Used[index135Used]||colums[c]){
continue;
}
diagonals45Used[index45Used]=diagonals135Used[index135Used]=colums[c] = true;
nQueens[r][c] = 'Q';
backTracking(lists,r+1);
nQueens[r][c] = '.';
diagonals45Used[index45Used]=diagonals135Used[index135Used]=colums[c] = false;
}
}
}
1424. 对角线遍历 II
45°
int length = 0;
//根据对角线i+j唯一且相同,LinkedHashMap保持插入排序。效率是最优的
Map<Integer,List<Integer>> map =new LinkedHashMap<>();
for(int i = 0;i < nums.size();i++) {
length += nums.get(i).size();
for(int j = 0;j < nums.get(i).size();j++) {
List<Integer> orDefault = map.getOrDefault(i + j, new ArrayList<>());
orDefault.add(nums.get(i).get(j));
map.putIfAbsent(i+j,orDefault);
}
}
int[] result = new int[length];
int index = 0;
//遍历map,得到结果。
for(int key : map.keySet()) {
List<Integer> list = map.get(key);
for(int j = list.size() - 1;j >= 0;j--) {
result[index] = list.get(j);
index++;
}
}
return result;
数据结构
树
437. 路径总和 III
//主要采取了 横向遍历+ 单点处理
class Solution {
private int pathSum = 0; //记录所以结果
public int pathSum(TreeNode root, int sum) {
LinkedList<TreeNode> levelNodes = new LinkedList<>();
if(root!=null){
levelNodes.add(root);
}
while(!levelNodes.isEmpty()){
int size = levelNodes.size();
for(int i=0;i<size;i++){
TreeNode node = levelNodes.remove();
if(node.left!=null){
levelNodes.add(node.left);
}
if(node.right!=null){
levelNodes.add(node.right);
}
getSum(node,sum);
}
}
return pathSum;
}
public void getSum(TreeNode root,int sum){
if(root==null)return;
if(root.val==sum)pathSum+=1;
getSum(root.left,sum-root.val);
getSum(root.right,sum-root.val);
}
}
572. 另一个树的子树_
//这题和437思想类似
isSubtree负责宏观遍历s每一个节点,isSubtreeWithNode对每个节点进行微观细找
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s==null)return false;
return isSubtree(s.left,t) || isSubtree(s.right,t) || isSubtreeWithNode(s,t);
}
public boolean isSubtreeWithNode(TreeNode s, TreeNode t){
if(s==null&&t==null)return true;
if(s==null||t==null)return false;
if(s.val!=t.val)return false;
return isSubtreeWithNode(s.left,t.left) && isSubtreeWithNode(s.right,t.right);
}
}
111. 二叉树的最小深度_
比最大深度要多考虑的就是,如果只有一个分支的话
public int minDepth(TreeNode root) {
if(root==null)return 0;
int l = minDepth(root.left);
int r = minDepth(root.right);
if(l==0||r==0){
return l+r+1; //咱得取有叶子节点的子树高度
}
return Math.min(l,r)+1;
}
110. 平衡二叉树_
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
class Solution {
public boolean isBalanced(TreeNode root) {
return height(root)>=0;
}
private int height(TreeNode root){
if(root==null)return 0;
int heightL = height(root.left);
int heightR = height(root.right);
// if(Math.abs(heightL-heightR)>0)return -1;
if(heightL==-1||heightR==-1||Math.abs(heightL-heightR)>1) return -1; //这个-1会一直被传递上去
//是平衡节点则返回高度
return Math.max(heightL,heightR)+1;
}
98. 验证二叉搜索树
//思路:利用中序遍历 输出值从小到大
class Solution {
private long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null)return true; //特殊情况:当前节点是null
if(!isValidBST(root.left)){ //如果产生false那就一直传递上去
return false;
}
//这儿的root.val是中序值,而此时的pre经过root.left的更新
if(root.val<=pre)return false; //产生false的出处
pre = Math.max(pre,root.val); //更新当前节点的最小下限(不可达)
return isValidBST(root.right);
}
}
958.完全二叉树校验:和树的宽度解法很像
class Solution {
public boolean isCompleteTree(TreeNode root) {
List<ANode> nodes = new ArrayList();
nodes.add(new ANode(root, 1));
int i = 0;
while (i < nodes.size()) {
ANode anode = nodes.get(i++);
if (anode.node != null) {
nodes.add(new ANode(anode.node.left, anode.code * 2));
nodes.add(new ANode(anode.node.right, anode.code * 2 + 1));
}
}
return nodes.get(i-1).code == nodes.size();
}
}
class ANode { // Annotated Node
TreeNode node;
int code;
ANode(TreeNode node, int code) {
this.node = node;
this.code = code;
}
}
662. 二叉树最大宽度
public int widthOfBinaryTree(TreeNode root) {
int res = 0;
LinkedList<Node> levelNodes = new LinkedList<>();
if(root!=null)levelNodes.add(new Node(root,1));
while(!levelNodes.isEmpty()){
res = Math.max(res,levelNodes.getLast().index-levelNodes.get(0).index+1);
int size = levelNodes.size();
for(int i = 0;i<size;i++){
Node remove = levelNodes.remove();
if(remove.node.left!=null){
Node left = new Node(remove.node.left,remove.index*2);
levelNodes.add(left);
}
if(remove.node.right!=null){
Node right = new Node(remove.node.right,remove.index*2+1);
levelNodes.add(right);
}
}
}
return res;
}
class Node{
TreeNode node;
int index; //记录位置
public Node(TreeNode node,int index){
this.node = node;
this.index = index;
}
}
337. 打家劫舍 III 间隔遍历
关注当前节点要什么就好。
public int rob(TreeNode root) {
if(root==null)return 0;
int val1= root.val;
if(root.left!=null)val1+=rob(root.left.left)+rob(root.left.right);
if(root.right!=null)val1+=rob(root.right.left)+rob(root.right.right);
int val2 = rob(root.left)+rob(root.right);
return Math.max(val1,val2);
}
层次遍历
public List<Double> averageOfLevels(TreeNode root) {
LinkedList<TreeNode> levelNodes = new LinkedList<>();
List<Double> list = new ArrayList<>();
if(root!=null)levelNodes.add(root);
while(!levelNodes.isEmpty()){
int size = levelNodes.size();
double temp = 0;
for(int i = 0;i<size;i++){
TreeNode remove = levelNodes.remove();
temp += remove.val;
if(remove.left!=null){
levelNodes.add(remove.left);
}
if(remove.right!=null){
levelNodes.add(remove.right);
}
}
list.add(temp / size);
}
return list;
}
非递归实现二叉树的前序遍历
//这个的想法和递归出栈有些类似
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<>();
if(root!=null)stack.push(root);
while(!stack.isEmpty()){
TreeNode remove = stack.pop(); //root点肯定得先弹出来
list.add(remove.val); //当前优先
if(remove.right!=null)stack.push(remove.right);
if(remove.left!=null)stack.push(remove.left); //其次左优先
}
return list;
}
非递归实现二叉树的后序遍历
前序:root->left->right 后序:left->right->root
将前序改成root -> right -> left其reverse即为后序
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<>();
if(root!=null)stack.push(root);
while(!stack.isEmpty()){
TreeNode remove = stack.pop();
list.add(remove.val);
if(remove.left!=null)stack.push(remove.left);
if(remove.right!=null)stack.push(remove.right);
}
Collections.reverse(list);
return list;
}
非递归实现二叉树的中序遍历
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<>();
while(root!=null||!stack.isEmpty()){
while(root!=null){ //利用循环下到节点的最左下角
stack.push(root);
root = root.left;
}
root = stack.pop();
list.add(root.val);
root = root.right; //看看有没有右节点
}
return list;
}
105. 从前序与中序遍历序列构造二叉树
```java
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
TreeNode root = new TreeNode(preorder[0]);
Deque<TreeNode> stack = new LinkedList<TreeNode>();
stack.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.length; i++) {
int preorderVal = preorder[i];
TreeNode node = stack.peek();
if (node.val != inorder[inorderIndex]) { //不等于栈顶即还有左,继续入栈
node.left = new TreeNode(preorderVal);
stack.push(node.left); //前序的顺序入栈左其出栈时和中序是一样的,不一样则遇见右
} else { //等于栈顶 即没有左,弹结束遇到右
while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}
二叉树的z字形遍历 面试官说:不使用队列,想想其他数据结构
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
travel(root, res, 0);
return res;
}
private void travel(TreeNode cur, List<List<Integer>> res, int level) {
if (cur == null)
return;
//如果res.size() <= level说明下一层的集合还没创建,所以要先创建下一层的集合
if (res.size() <= level) {
List<Integer> newLevel = new LinkedList<>();
res.add(newLevel);
}
//遍历到第几层我们就操作第几层的数据
List<Integer> list = res.get(level);
//这里默认根节点是第0层,偶数层相当于从左往右遍历,
// 所以要添加到集合的末尾,如果是奇数层相当于从右往左遍历,
// 要把数据添加到集合的开头
if (level % 2 == 0)
list.add(cur.val);
else
list.add(0, cur.val);
//分别遍历左右两个子节点,到下一层了,所以层数要加1
travel(cur.left, res, level + 1);
travel(cur.right, res, level + 1);
}
669. 修剪二叉搜索树
要利用函数的意思,再配合正向思考逻辑
//二叉搜索树 左小 右大 当前居中
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null)return null;
//当前不满足范围要求,往里面看
if(root.val>high)return trimBST(root.left,low,high);
if(root.val<low)return trimBST(root.right,low,high);
//当前满足范围要求,往两边看
root.left=trimBST(root.left,low,high);
root.right=trimBST(root.right,low,high);
return root;
}
230. 二叉搜索树中第K小的元素
用中序遍历(非递归方式)
public int kthSmallest(TreeNode root, int k) {
Stack<TreeNode>stack = new Stack<>();
if(root==null)return 0;
while(root!=null||!stack.empty()){
while(root!=null){
stack.push(root);
root = root.left;
}
TreeNode remove = stack.pop();
k --;
if(k==0)return remove.val;
root = remove.right;
}
return 0;
}
//递归方式 为保持递归中序特性,成员变量持久记录更新替换最初的print(输出持久化)
private int cnt = 0;
private int val;
public int kthSmallest(TreeNode root, int k) {
inOrder(root, k);
return val;
}
private void inOrder(TreeNode node, int k) {
if (node == null) return;
inOrder(node.left, k);
cnt++;
if (cnt == k) {
val = node.val;
return;
}
inOrder(node.right, k);
}
538. 把二叉搜索树转换为累加树
二叉搜索树中序遍历时从小到大 但是吧本题得累加大的数
利用中序的倒序方式
private int sum = 0;
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
private void dfs(TreeNode root){
if(root==null)return;
dfs(root.right); //为了将右边的先加上
sum +=root.val; //成员变量持久记录
root.val = sum;
dfs(root.left);
}
129.求根节点到叶节点数字之和
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int prevSum) {
if (root == null) {
return 0;
}
int sum = prevSum * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
} else {
return dfs(root.left, sum) + dfs(root.right, sum);
}
}
1530. 好叶子节点对的数量
后序的方式 先left right 再root
class Solution {
private int ans;
public int countPairs(TreeNode root, int distance) {
dfs(root,distance);
return ans;
}
private ArrayList<Integer> dfs(TreeNode node,int distance){
if(node==null)return new ArrayList(); //不能return null要不空指针
ArrayList<Integer> ret = new ArrayList();
if(node.left==null&&node.right==null){
ret.add(1);
return ret;
}
ArrayList<Integer>left = dfs(node.left,distance);
for(int n:left){
if(++n>distance)continue; //当前 为左右孩子的路径+1
ret.add(n);
}
ArrayList<Integer>right = dfs(node.right,distance);
for(int n:right){
if(++n>distance)continue;
ret.add(n);
}
for(int l:left){ //当前的 只看左与右的匹配
for(int r:right){
ans +=(l+r<=distance?1:0);
}
}
return ret;
}
}
687. 最长同值路径
最大路径和问题
private int pathNum = 0;
public int longestUnivaluePath(TreeNode root) {
partPath(root);
return pathNum;
}
private int partPath(TreeNode root){
if(root==null)return 0;
int l = partPath(root.left);
int r = partPath(root.right);
int pathL = root.left!=null&&root.left.val==root.val?l+1:0;
int pathR = root.right!=null&&root.right.val==root.val?r+1:0;
pathNum = Math.max(pathNum,pathL+pathR);
return Math.max(pathL,pathR);
}
124. 二叉树中的最大路径和
class Solution {
private int pathSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxPathRecord(root);
return pathSum;
}
private int maxPathRecord(TreeNode root){
if(root==null)return 0;
int left = Math.max(maxPathRecord(root.left),0);
int right = Math.max(maxPathRecord(root.right),0);
int newPathSum= root.val + left + right; //当前点的路径和
pathSum = Math.max(newPathSum,pathSum); //更新最大路径
return root.val+Math.max(left,right); //返回当前节点所能提供的最大贡献度
}
}
235. 二叉搜索树的最近公共祖先_
对比236
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p,q);
//在不同边直接返回
return root;
}
236. 二叉树的最近公共祖先
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//特殊情况
if(root==null||root==p||root==q)return root;
//开始往下找
TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
if(leftNode!=null&&rightNode!=null) return root; //(1)在当前的子树下
return leftNode!=null?leftNode:rightNode; //将非null的保留 随出栈肯定会满足(1)
}
}
109. 有序链表转换二叉搜索树
利用快慢指针找链表中点,然后分治
public TreeNode sortedListToBST(ListNode head) {
//1. 特例处理
if (head == null){
return null;
}else if(head.next == null){
return new TreeNode(head.val);
}
//2. 利用快慢指针找链表中间节点
ListNode slow = head, fast = head;
ListNode pre = null;
while( fast != null && fast.next != null){
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
//3. 创建树的根节点,并断开相应连接
TreeNode root = new TreeNode(slow.val);
pre.next = null;
//4. 递归链表中间节点左右两边的子链表
root.left = sortedListToBST(head);
root.right = sortedListToBST(slow.next);
return root;
}
653. 两数之和 IV - 输入 BST_
中序的结果是有顺序的 然后从两边各自开始微调
public boolean findTarget(TreeNode root, int k) {
List<Integer> list = new ArrayList<>();
inorder(root,list);
int i = 0, j = list.size()-1;
while(i<j){
int sum = list.get(i)+list.get(j);
if(sum==k)return true;
if(sum<k)i++;
else j--;
}
return false;
}
private void inorder(TreeNode root,List<Integer>list){
if(root==null)return;
inorder(root.left,list);
list.add(root.val);
inorder(root.right,list);
}
208. 实现 Trie (前缀树)
字典树:用于存储查找有公告前缀的字符串
每个字符所在位置 都是一个26位的数组,前一个字符指向后一个字符所在的数组
路径上每个分支都代表一个字符
class Trie {
private class Node{
Node[] childs = new Node[26];
boolean isLeaf;
}
private Node root = new Node();
/** Initialize your data structure here. */
public Trie() {
}
/** Inserts a word into the trie. */
public void insert(String word) {
insert(word,root);
}
private void insert(String word,Node node){ //在数组中找位置
if(node==null)return;
if(word.length()==0){ //插完事了
node.isLeaf =true; //给个标致 为true的构成一个完整字符串
return;
}
int index = indexForChar(word.charAt(0));
if(node.childs[index]==null){ //要插入的位置没有元素
node.childs[index] = new Node(); //插入 即 该位置指向下一个字符所在数组
}
insert(word.substring(1),node.childs[index]);
}
private int indexForChar(char c){ //位置计算
return c-'a';
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
return search(word,root);
}
private boolean search(String word,Node node){
if(node==null)return false;
if(word.length()==0){
return node.isLeaf; //有两种可能 true就是当初有这个字符串
}
int index = indexForChar(word.charAt(0));
return search(word.substring(1),node.childs[index]);
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
return startsWith(prefix,root);
}
public boolean startsWith(String prefix,Node node){
if(node==null)return false;
if(prefix.length()==0){ //与找字符串唯一区别就在这
return true;
}
int index = indexForChar(prefix.charAt(0));
return startsWith(prefix.substring(1),node.childs[index]);
}
}
677. 键值映射
class MapSum {
/** Initialize your data structure here. */
public MapSum() {
}
private class Node{
Node[] childs = new Node[26];
int val;
}
private Node root = new Node();
public void insert(String key, int val) {
insert(key,val,root);
}
public void insert(String key,int val,Node node){
if(node==null)return;
if(key.length()==0){
node.val = val;
return;
}
int index = indexForChar(key.charAt(0));
if(node.childs[index]==null){
node.childs[index] = new Node();
}
insert(key.substring(1),val,node.childs[index]);
}
private int indexForChar(char c){
return c-'a';
}
public int sum(String prefix) {
return sum(prefix,root);
}
public int sum(String prefix, Node node){
if(node==null)return 0;
if(prefix.length()!=0){ //消耗完prefix
int index = indexForChar(prefix.charAt(0));
return sum(prefix.substring(1),node.childs[index]); //消耗完prefix的那个return最先返回
}
int sum = node.val; //被加的值
for(Node child:node.childs){ //对每个节点进行
if(node!=null){
sum+= sum(prefix,child);
}
}
return sum;
}
}
440. 字典序的第K小数字
十叉树
public int findKthNumber(int n, int k) {
long cur = 1;
k -= 1;
while(k>0){
long steps = getCount(cur,n);
if(k>=steps){
k -= steps;
cur += 1; //去后一个 兄弟
}else{
k -= 1;
cur *= 10; //去下一层 子
}
}
return (int)cur;
}
private int getCount(long cur,int n){ //前后两兄弟的序号差值
long nextCur = cur +1;
long steps = 0;
while(cur<=n){
steps += Math.min(nextCur-cur,n-cur+1);
nextCur *= 10;
cur *= 10;
}
return (int)steps;
}
字符串
647 回文字符串
思路:字符串长度分两类:奇、偶 以每个初始为出发点向两边延展寻找更多可能
class Solution {
private int cnt;
public int countSubstrings(String s) {
for(int i=0;i<s.length();i++){
extendString(s,i,i); //奇数长度
extendString(s,i,i+1); //偶数长度
}
return cnt;
}
private void extendString(String s,int start, int end){
while(start>=0&&end<s.length()&&s.charAt(start)==s.charAt(end)){ //向两边延展
cnt ++;
start --;
end++;
}
}
}
205. 同构字符串_
思路:字符之间存在映射,所以考虑HashMap
public boolean isIsomorphic(String s, String t) {
HashMap<Character,Character> s2t = new HashMap<>();
HashMap<Character,Character> t2s = new HashMap<>();
for(int i=0;i<s.length();i++){
char x = s.charAt(i);
char y = t.charAt(i);
if(s2t.containsKey(x)&&s2t.get(x)!=y||t2s.containsKey(y)&&t2s.get(y)!=x){
return false;
}
s2t.put(x,y);
t2s.put(y,x);
}
return true;
}
9. 回文数_
字符串解法
public boolean isPalindrome(int x) {
if(x<0)return false;
String str = String.valueOf(x);
char[] chars = str.toCharArray();
int end = str.length()-1;
int start = 0;
while(start<end){
if(chars[start]!=chars[end])return false;
start++;
end --;
}
return true;
}
整数解法
public boolean isPalindrome(int x) {
if (x == 0) {
return true;
}
if (x < 0 || x % 10 == 0) { //负数或者个位为0的正整数
return false;
}
int right = 0;
while (x > right) { //若剩余的高位还高
right = right * 10 + x % 10;
x /= 10;
}
return x == right || x == right / 10;
}
696计数二进制_
public int countBinarySubstrings(String s) {
int ptr = 0,count=0,last=0,cnt=0;
while(ptr<s.length()){
char c = s.charAt(ptr);
count = 0;
while(ptr<s.length()&&s.charAt(ptr)==c){ //寻找当前字符串的连续数
++ptr;
++count;
}
cnt += Math.min(count,last); //取之前字符串连续数与当期连续数的小值
last = count;
}
return cnt;
}
567. 字符串的排列
s1 的排列之一是 s2 的 子串 。
public boolean checkInclusion(String s1, String s2) {
int m = s1.length(), n = s2.length();
if(m>n)return false;
int[] cnt = new int[26];
for(char c:s1.toCharArray()){
--cnt[c-'a'];
}
int left = 0;
for(int right=0;right<n;right++){
int x = s2.charAt(right) - 'a';
++cnt[x];
while(cnt[x]>0){ //目标抵消就好 即cnt[x]=0
--cnt[s2.charAt(left)-'a']; //左边收窄
++left;
}
if(right-left+1==m){
return true;
}
}
return false;
}
76. 最小覆盖子串
参考LC567. 字符串的排列 但最后一个用例(巨长的字符串)没通过
public String minWindow(String s, String t) {
int m = s.length(), n = t.length();
String ans = "";
int index = m+1;
if(m<n) return ans;
int[] cnt = new int[128];
for(char c:t.toCharArray()){
--cnt[c];
}
int left = 0;
for(int right=0;right<m;right++){
++cnt[s.charAt(right)];
while(left<=right&&cnt[s.charAt(left)]>0){
--cnt[s.charAt(left)];
left++;
}
if(check(cnt,t)){ //满足要求的
if(index>right-left+1){
ans = s.substring(left,right+1);
index = right-left+1;
}
}
}
return ans;
}
private boolean check(int[]cnt,String t){
for(char c:t.toCharArray()){
if(cnt[c]<0)return false;
}
return true;
}
402. 移掉 K 位数字
参考link
public String removeKdigits(String num, int k) {
LinkedList<Integer>stack = new LinkedList();
for(char c:num.toCharArray()){
int x = c - '0';
while(!stack.isEmpty()&&x<stack.peek()&&k-->0){
stack.pop();
}
if(stack.isEmpty()&&x==0){ //栈为空,'0'就不放了
// k--;
continue;
}
stack.push(x);
}
while(!stack.isEmpty()&&k-->0){ //还没有删除足够元素 从后面出栈删除大元素
stack.pop();
}
if(stack.isEmpty())return "0";
StringBuilder sb = new StringBuilder();
while(!stack.isEmpty()){
sb.insert(0,stack.pop());
}
return sb.toString();
}
1405. 最长快乐字符串
public String longestDiverseString(int a, int b, int c) {
MyChar[]myChars = new MyChar[]{
new MyChar('a',a),
new MyChar('b',b),
new MyChar('c',c)
};
StringBuilder sb = new StringBuilder();
while(true){
Arrays.sort(myChars);
int size = sb.length();
//放完两个字符还是最多
if(size>=2&&sb.charAt(size-1)==myChars[2].ch&&sb.charAt(size-2)==myChars[2].ch){
if(myChars[1].cnt-->0){
sb.append(myChars[1].ch); //放次多元素
}else{
break;
}
}else{
if(myChars[2].cnt-->0){
sb.append(myChars[2].ch);
}else{
break;
}
}
}
return sb.toString();
}
private class MyChar implements Comparable{
char ch;
int cnt;
public MyChar(char ch,int cnt){
this.ch = ch;
this.cnt = cnt;
}
@Override
public int compareTo(Object o){
MyChar other = (MyChar)o;
return this.cnt - other.cnt; //升序
}
}
小点补充
1.利用数组当前字符的个数
for(char c : s.toCharArray()){
cnts[c-'A']++;
}
链表
160. 相交链表_
每个节点只有一个后继节点,即若有相交节点的话,则该节点后 A B 都一样
交换访问的方式,规避一致链前长度不一样的问题
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1 = headA;
ListNode l2 = headB;
while(l1!=l2){
l1 = (l1==null)?headB:l1.next;
l2 = (l2==null)?headA:l2.next;
}
return l1;
}
NC3 链表中环的入口结点
方法一:使用一个HashSet,遍历链表,当出现重复节点即有环,立即返回
import java.util.*;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null)
return pHead;
Set<ListNode> set = new HashSet<>();
while(pHead != null){
if(set.contains(pHead)){
return pHead;
}
set.add(pHead);
pHead = pHead.next;
}
return null;
}
}
方法二:快慢指针
分析:根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
有了 a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
public ListNode EntryNodeOfLoop(ListNode pHead) {
if(pHead==null)return null;
ListNode slow = pHead,fast = pHead;
while(fast!=null){
slow = slow.next;
if(fast.next!=null){
fast = fast.next.next;
}else{
return null;
}
if(fast==slow){ //相遇啦
ListNode ptr = pHead;
while(ptr!=slow){
ptr=ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
206. 反转链表_
//头插法
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
92. 反转链表 II
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
int i = 0;
ListNode newHead=pre,newTail = pre;
while(i<right){ //先找到要翻转的区间
newTail = newTail.next;
if(i<left){
pre = newHead;
newHead = newHead.next;
}
i++;
}
ListNode next = newTail.next;
newTail.next = null;
ListNode[]reverse = reverse(newHead);
pre.next = reverse[0];
reverse[1].next = next;
return dummy.next;
}
private ListNode[] reverse(ListNode head){
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
cur = temp;
}
return new ListNode[]{prev,head};
}
25. K 个一组翻转链表
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy, end = dummy;
while(end.next!=null){
for(int i=0;i<k&&end!=null;i++)end = end.next;
if(end==null)break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null; //切断
pre.next = reverse(start);
start.next = next;
pre = start;
end = pre;
}
return dummy.next;
}
private ListNode reverse(ListNode head) { //头插法
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
61. 旋转链表
将链表每个节点向右移动 k 个位置。
if (head == null || head.next == null) {
return head;
}
int len = 1, index;
ListNode temp = head, newHead;
while (temp.next != null) { //计算链表长度len
len++;
temp = temp.next;
}
temp.next = head; //将链表连接成循环链表
k %= len; //旋转链表每len次循环一次,因此计算k对len的取余,避免重复操作
index = len - k; //找到要断开循环链表的节点纪录链表新的头结点
while (index-- > 0) {
temp = temp.next;
}
newHead = temp.next;
temp.next = null; //断开循环链表
return newHead;
19.删除链表的倒数第 n 个节点
用保持间隔的快慢指针
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head;
while(n-->0){ //保持间隔(索引差值)
fast = fast.next;
}
if(fast==null)return head.next;
ListNode slow = head;
while(fast.next!=null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
82. 删除排序链表中的重复元素 II
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while(pre.next!=null&&pre.next.next!=null){
if(pre.next.val==pre.next.next.val){
int x = pre.next.val;
while(pre.next!=null&&pre.next.val==x){
pre.next = pre.next.next;
}
}else{
pre = pre.next;
}
}
return dummy.next;
}
24. 两两交换链表中的节点
把握好pre
public ListNode swapPairs(ListNode head) {
ListNode node = new ListNode(-1);
node.next = head;
ListNode pre = node; //
while(pre.next!=null&&pre.next.next!=null){
ListNode l1 = pre.next;
ListNode l2 = pre.next.next;
pre.next = l2;
ListNode next = l2.next;
l2.next = l1;
l1.next = next;
pre = l1;
}
return node.next;
}
148. 排序链表归并排序(递归)
public ListNode sortList(ListNode head) {
return sortList(head,null);
}
private ListNode sortList(ListNode head,ListNode tail){
if(head==null)return head;
if(head.next==tail){
head.next = null; //这样处理源于拆分的时候都包含了mid节点
return head;
}
ListNode slow = head, fast = head;
while(fast!=tail){
slow = slow.next;
fast = fast.next;
if(fast!=tail){
fast = fast.next;
}
}
ListNode mid = slow;
ListNode list1 = sortList(head,mid);
ListNode list2 = sortList(mid,tail);
return merge(list1,list2);
}
private ListNode merge(ListNode headA,ListNode headB){
ListNode dummyHead = new ListNode(-1);
ListNode temp = dummyHead,temp1 = headA,temp2 = headB;
while(temp1!=null&&temp2!=null){
if(temp1.val<=temp2.val){
temp.next = temp1;
temp = temp.next;
temp1 = temp1.next;
}else{
temp.next = temp2;
temp = temp.next;
temp2 = temp2.next;
}
}
temp.next = temp1==null?temp2:temp1;
return dummyHead.next;
}
插入排序
public ListNode sortList(ListNode head) {
if (head == null) {
return head;
}
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode lastSorted = head, curr = head.next;
while (curr != null) {
if (lastSorted.val <= curr.val) {
lastSorted = lastSorted.next;
} else {
ListNode prev = dummyHead;
while (prev.next.val <= curr.val) {
prev = prev.next;
}
lastSorted.next = curr.next;
curr.next = prev.next;
prev.next = curr;
}
curr = lastSorted.next;
}
return dummyHead.next;
}
23. 合并K个升序链表
最小堆
public ListNode mergeKLists(ListNode[] lists) {
if(lists==null||lists.length==0)return null;
ListNode preHead = new ListNode(-1),pre = preHead;
int k = lists.length;
PriorityQueue<ListNode>minHeap = new PriorityQueue<>(k,(a,b)->a.val-b.val);
for(ListNode node:lists){
if(node!=null){
minHeap.add(node);
}
}
while(!minHeap.isEmpty()){
ListNode removeNode = minHeap.remove();
if(removeNode.next!=null){
minHeap.add(removeNode.next);
}
pre.next = removeNode;
pre = pre.next;
}
return preHead.next;
}
简单版本:21. 合并两个有序链表
143. 重排链表
public void reorderList(ListNode head) {
if(head==null)return;
ListNode mid = midNode(head);
ListNode l2 = mid.next;
mid.next = null;
l2 = reverse(l2);
merge(head,l2);
}
private ListNode midNode(ListNode head){ //取中
ListNode slow = head,fast = head;
while(fast.next!=null&&fast.next.next!=null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
private ListNode reverse(ListNode head){ //翻转
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
private void merge(ListNode l1,ListNode l2){
while(l1!=null&&l2!=null){
ListNode l1_temp = l1.next;
l1.next = l2;
ListNode l2_temp = l2.next;
l2.next = l1_temp;
l1 = l1_temp;
l2 = l2_temp;
}
}
445. 两数相加 II
不要用Stack(底层是用Vector实现的)执行击败41% 内存击败37%
改用LinkedList 81% 61%
这题高位在首节点,所以需要用栈,先低位出栈运算
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
LinkedList<Integer>stack1 = buildStack(l1);
LinkedList<Integer>stack2 = buildStack(l2);
int carry = 0;
ListNode head = new ListNode(-1);
while(!stack1.isEmpty() ||!stack2.isEmpty()||carry!=0){
int val1 = stack1.isEmpty()?0:stack1.pop();
int val2 = stack2.isEmpty()?0:stack2.pop();
//头插法
ListNode node = new ListNode((val1+val2+carry)%10);
node.next = head.next;
head.next = node;
carry = (val1+val2+carry)/10;
}
return head.next;
}
private LinkedList<Integer> buildStack(ListNode l){
LinkedList<Integer> stack = new LinkedList<>();
while(l!=null){
stack.push(l.val);
l = l.next;
}
return stack;
}
234. 回文链表_
快慢指针 2倍速切开
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) return true;
ListNode slow = head, fast = head.next;
while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next; //2倍速
}
if(fast!=null) slow = slow.next; //奇数
cut(head,slow);
return isEqual(head,reverse(slow));
}
private void cut(ListNode head,ListNode cutNode){
while(head.next!=cutNode){
head = head.next;
}
head.next = null; //切断联系
}
//头插法翻转链表
private ListNode reverse(ListNode node){
ListNode preHead = new ListNode(-1);
while(node!=null){
ListNode temp = node.next;
node.next = preHead.next;
preHead.next = node;
node = temp;
}
return preHead.next;
}
private boolean isEqual(ListNode l1, ListNode l2){
while(l1!=null&&l2!=null){
if(l1.val!=l2.val)return false;
l1 = l1.next;
l2 = l2.next;
}
return true;
}
725. 分隔链表
public ListNode[] splitListToParts(ListNode root, int k) {
int N = 0;
ListNode cur = root;
while(cur!=null){ //计数
N ++;
cur = cur.next;
}
int mode = N % k;
int size = N / k;
ListNode[] ret = new ListNode[k];
cur = root;
for(int i=0;cur!=null&&i<k;i++){
ret[i] = cur;
int curSize = size + (mode-->0?1:0);
for(int j=0;j<curSize-1;j++){
cur = cur.next;
}
ListNode temp = cur.next;
cur.next = null; //斩断
cur = temp;
}
return ret;
}
328. 奇偶链表
public ListNode oddEvenList(ListNode head) {
if(head==null)return head;
ListNode odd = head, even = head.next, evenHead = even;
while(even!=null&&even.next!=null){ //even是偶数
odd.next = odd.next.next; //连上下一个奇位置
odd = odd.next; //走到下一个奇位置(非空)
even.next = even.next.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
排序奇升偶降链表
栈和队列
739. 每日温度
没有大的就先将索引放在栈中(后进栈一定比栈中现存的值小), 遇到大的就将之前保存的较小的都出栈
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] ret = new int[n];
LinkedList<Integer> stack = new LinkedList<>();
for(int curIndex = 0;curIndex<n;curIndex++){
while(!stack.isEmpty() && temperatures[curIndex]>temperatures[stack.peek()]){
int preIndex = stack.pop();
ret[preIndex] = curIndex - preIndex;
}
stack.push(curIndex);
}
return ret;
}
503. 下一个更大元素 II
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] ret = new int[n];
Arrays.fill(ret,-1);
LinkedList<Integer>stack = new LinkedList<>();
for(int i=0;i<n*2;i++){
int num = nums[i%n];
while(!stack.isEmpty() && num > nums[stack.peek()]){
ret[stack.pop()] = num;
}
if(i<n){
stack.push(i);
}
}
return ret;
}
394. 字符串解码
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int multi = 0;
LinkedList<Integer> stack_multi = new LinkedList<>();
LinkedList<String> stack_res = new LinkedList<>();
for(Character c : s.toCharArray()) {
if(c == '[') {
stack_multi.addLast(multi);
stack_res.addLast(res.toString());
//清零处理
multi = 0;
res = new StringBuilder();
}
else if(c == ']') {
StringBuilder tmp = new StringBuilder();
int cur_multi = stack_multi.removeLast();
for(int i = 0; i < cur_multi; i++) tmp.append(res);
res = new StringBuilder(stack_res.removeLast() + tmp);//将字符串与内层结果拼接
}
else if(c >= '0' && c <= '9') multi = multi * 10 + Integer.parseInt(c + ""); //计数
else res.append(c);
}
return res.toString();
}
计算器
227. 基本计算器 II
只有±*/吴3括号
public int calculate(String s) {
//P108中缀表达式转后缀表达式 然后计算
LinkedList<Character> operator_stack = new LinkedList();//操作符栈
LinkedList<Integer> number_stack = new LinkedList(); //操作数栈
Set<Character>signs = new HashSet(Arrays.asList('+','-','*','/'));
int n = s.length();
int idx = 0;
while(idx<n){
if(Character.isDigit(s.charAt(idx))){
int num = s.charAt(idx++)-'0';
while(idx<n&&Character.isDigit(s.charAt(idx))){
num = num*10 + s.charAt(idx)-'0';
++idx;
}
number_stack.push(num); //后缀表达式 遇到数字直接入栈
}else if(signs.contains(s.charAt(idx))){
while(!number_stack.isEmpty()&&!operator_stack.isEmpty()&&priority(operator_stack.peek(),s.charAt(idx))){//当前操作符优先级不高于栈顶
calculate(operator_stack,number_stack);//先出栈计算
}
operator_stack.push(s.charAt(idx)); //再把当前运算符放到操作符栈中去
++idx;
}else if(s.charAt(idx)==' ')++idx;
}
while(!operator_stack.isEmpty())calculate(operator_stack,number_stack);
return number_stack.peek();
}
private void calculate(LinkedList<Character> operator_stack,LinkedList<Integer> number_stack){
// 拿出来操作数栈中顶层的两个数,再把操作符栈中顶层的符号取出来,进行计算后放到操作数栈中
char operator = operator_stack.pop();
int num1 = number_stack.pop(),num2 = number_stack.pop();
if(operator=='+')number_stack.push(num2+num1);
else if(operator=='-')number_stack.push(num2-num1);
else if(operator=='*')number_stack.push(num2*num1);
else number_stack.push(num2/num1);
}
private boolean priority(char top,char curr){ //不比栈顶优先级高方为true
if(curr==top)return true;
else if(top=='*'||top=='/')return true;
else return (top=='+'||top=='-')&&(curr=='+'||curr=='-');
}
哈希表
1. 两数之和_
用空间换时间
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer>indexForNum = new HashMap();
for(int i=0;i<nums.length;i++){
if(indexForNum.containsKey(target-nums[i])){
return new int[]{indexForNum.get(target-nums[i]),i};
}else{
indexForNum.put(nums[i],i); //值-索引
}
}
return null;
}
138. 复制带随机指针的链表
Map<Node, Node> cachedNode = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
if (!cachedNode.containsKey(head)) {
Node headNew = new Node(head.val);
cachedNode.put(head, headNew);
//先递归完所有的next 防止随机指针指向的节点不存在
headNew.next = copyRandomList(head.next);
headNew.random = copyRandomList(head.random);
}
return cachedNode.get(head);
}
128. 最长连续序列
public int longestConsecutive(int[] nums) {
Map<Integer,Integer>map = new HashMap();
for(int num:nums){
map.put(num,1);
}
for(int num:nums){
forward(map,num);
}
return maxCount(map,nums);
}
private int forward(Map<Integer,Integer>map,int num){
if(!map.containsKey(num)){
return 0;
}
int cnt = map.get(num);
if(cnt>1){ //指第一轮遍历完事后已经存储过,就不用再次遍历了
return cnt;
}
cnt = forward(map,num+1)+1; //求取当前元素可达的最大连续长度
map.put(num,cnt);
return cnt;
}
private int maxCount(Map<Integer,Integer>map,int[] nums){
int max =0;
for(int num:nums){
max = Math.max(max,map.get(num));
}
return max;
}
//这个方法相对方法一消耗高一点
public int longestConsecutive(int[] nums) {
Set<Integer>set = new HashSet();
for(int num:nums){
set.add(num);
}
int res = 0;
for(int x:nums){
if(!set.contains(x-1)){
int y = x;
while(set.contains(y+1)){
y++;
}
res = Math.max(res,y-x+1);
}
}
return res;
}
数组与矩阵
565. 数组嵌套
public int arrayNesting(int[] nums) {
int max = 0;
for(int i=0;i<nums.length;i++){
int cnt = 0;
for(int j=i;nums[j]!=-1;){
int t = nums[j];
cnt ++;
nums[j] = -1; // 标记该位置已经被访问
j = t;
}
max = Math.max(max,cnt);
}
return max;
}
162. 寻找峰值
线性扫描
public int findPeakElement(int[] nums) {
for(int i=0;i<nums.length-1;i++){
if(nums[i]>nums[i+1])return i; //题目只需要返回一个,此刻nums[i-1] < nums[i],要不直接return了
}
return nums.length -1;
}
31. 下一个排列
class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length,i = n-2;
while(i>=0&&nums[i]>=nums[i+1]){//从后面开始查找,不满足升序的元素
i--;
}
if(i>=0){
int j = n -1;
while(j>i&&nums[j]<=nums[i]){ //找出一个稍大的交换
j--;
}
swap(nums,i,j);
}
reverse(nums,i+1);
}
private void swap(int[]nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[]nums,int start){
int end = nums.length-1;
while(end>start){
swap(nums,start,end);
end--;
start++;
}
}
}
类似题目 556. 下一个更大元素 III
53. 最大子序和_
public int maxSubArray(int[] nums) {
if(nums.length==0)return 0;
int preSum = nums[0];
int maxSum = preSum;
for(int i=1;i<nums.length;i++){
//一旦preSum不大于0,前面的那些累积就不需要了
preSum = preSum>0?preSum+nums[i]:nums[i];
maxSum = Math.max(maxSum,preSum);
}
return maxSum;
}
862. 和至少为 K 的最短子数组
前缀数组 303. 区域和检索 - 数组不可变
放栈有点像 739. 每日温度
public int shortestSubarray(int[] nums, int k) {
int n = nums.length;
int[]sums = new int[n+1];
for(int i=0;i<n;i++)sums[i+1]=sums[i]+nums[i]; //构建前缀数组
LinkedList<Integer>deque = new LinkedList(); //ps 栈是在双端队列头部进行 push and pop
int minLen = Integer.MAX_VALUE;
for(int i=0;i<=n;i++){
while(!deque.isEmpty()&&sums[i]<=sums[deque.peekLast()])deque.pollLast();//我若比你小你根本没有优势
while(!deque.isEmpty()&&sums[i]-sums[deque.peekFirst()]>=k){
minLen = Math.min(minLen,i-deque.pollFirst());
}
deque.offer(i);
}
return minLen==Integer.MAX_VALUE?-1:minLen;
}
209. 长度最小的子数组:和大于target的连续子序列的最小长度。
滑动窗口
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
int start = 0, end = 0;
int sum = 0;
while (end < n) {
sum += nums[end];
while (sum >= s) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
一个无序数组,找到一个数,左边都比他小,右边都比他大
正遍历当前最大 标记
逆遍历当前最小 标记
public int deal(int[]arr){
int[] mos = new int[arr.length];
findMiddle(arr,mos);
for (int i = 0; i < arr.length; i++) {
if (mos[i] == 2) {
return arr[i];
}
}
}
private void findMiddle(int[] arr,int[] mos) {
if (null == arr || arr.length <= 2) {
return;
}
int max = 0;
int min = arr.length-1;
for (int i = 0; i < arr.length; i++) {
max=arr[max]<=arr[i]?i:max;
if (max == i) {
mos[i]++;
}
}
for (int i = arr.length-1; i >=0; i--) {
min=arr[min]>=arr[i]?i:min;
if (min == i) {
mos[i]++;
}
}
}
421. 数组中两个数的最大异或值
参考link
public class Solution {
// 先确定高位,再确定低位(有点贪心算法的意思),才能保证这道题的最大性质
// 一位接着一位去确定这个数位的大小
// 利用性质: a ^ b = c ,则 a ^ c = b,且 b ^ c = a
public int findMaximumXOR(int[] nums) {
int res = 0;
int mask = 0;
for (int i = 30; i >= 0; i--) {
// 注意点1:注意保留前缀的方法,mask 是这样得来的
// 用异或也是可以的 mask = mask ^ (1 << i);
mask = mask | (1 << i);
// System.out.println(Integer.toBinaryString(mask));
Set<Integer> set = new HashSet<>();
for (int num : nums) {
// 注意点2:这里使用 & ,保留前缀的意思(从高位到低位)
set.add(num & mask);
}
// 这里先假定第 n 位为 1 ,前 n-1 位 res 为之前迭代求得
int temp = res | (1 << i);
for (Integer prefix : set) {
if (set.contains(prefix ^ temp)) { //包含的话才说明temp可获取到,疑惑性质决定
res = temp;
break;
}
}
}
return res;
}
}
求区间最小数x区间和的最大值
X= min(subArray)*sum(subArray)
前缀和、单调栈
for(int i = 0; i < n; i ++) {
while(!stack.isEmpty() && a[i] <= a[stack.peek()]) {//我若比你小,下个区间就由我来了
int peak = a[stack.peek()];
stack.pop(); //比当前大的都被弹出去了
int l = stack.isEmpty()? -1 :stack.peek();
int r = i;
//l和r是边界,因此区间是[l+1,r-1]
int dist = sums[r] - sums[l+1];
res = max(res,peak*dist);
}
stack.push(i); //放入当前元素索引
}
while(!s.empty()){
int peak = a[stack.peek()];
stack.pop();
int l = stack.isEmpty()? -1 :stack.peek();
int r = n;
int dist = sums[r] - sums[l+1];
res = max(res,peak*dist);
}
for遍历
while(a[i)当前元素小于栈顶元素则出栈作为left,right就为i 然后更新结果
否则入栈索引i
769. 最多能完成排序的块 数组分隔
该数组元素特殊 0~arr.length - 1
public int maxChunksToSorted(int[] arr) {
if(arr==null)return 0;
int right = arr[0];
int cnt = 0;
for(int i=0;i<arr.length;i++){
right = Math.max(right,arr[i]);
if(right==i)cnt++;
}
return cnt;
}
303. 区域和检索 - 数组不可变
private int[] sums;
public NumArray(int[] nums) {
sums = new int[nums.length+1];
for(int i=0;i<nums.length;i++){
sums[i+1] = sums[i]+nums[i]; //表示之前的元素之和
}
}
public int sumRange(int left, int right) {
return sums[right+1]-sums[left];
}
304. 二维区域和检索 - 矩阵不可变
类似303. 区域和检索 - 数组不可变
class NumMatrix {
int[][]sums;
public NumMatrix(int[][] matrix) {
int m = matrix.length;
if(m>0){
int n = matrix[0].length;
sums = new int[m+1][n+1];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
sums[i+1][j+1] = sums[i+1][j] + sums[i][j+1] - sums[i][j] + matrix[i][j];
}
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return sums[row2+1][col2+1] - sums[row1][col2+1] -sums[row2+1][col1] + sums[row1][col1];
}
}
240. 搜索二维有序矩阵 II
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix==null||matrix[0]==null)return false;
int m = matrix.length;
int n = matrix[0].length;
int row =0, col = n-1; //初始放在了最右上角
while(row<m&&col>=0){
if(matrix[row][col]==target)return true;
if(matrix[row][col]>target){
col--;
}else{
row++;
}
}
return false;
}
378. 有序矩阵中第 K 小的元素
小顶堆解法,主要结合了该排序矩阵的升序特性
public int kthSmallest(int[][] matrix, int k) {
int m = matrix.length, n = matrix[0].length;
PriorityQueue<Tuple> smallHeap = new PriorityQueue();
for(int i=0;i<n;i++){ //先加入第0行元素
smallHeap.offer(new Tuple(0,i,matrix[0][i]));
}
for(int i=0;i<k-1;i++){ //小顶堆 弹出k-1个
Tuple top = smallHeap.poll(); //每次弹出最小的
if(top.x==m-1)continue;
//将弹出元素的下一行元素加入堆中,以保证堆顶是全局次小元素
smallHeap.offer(new Tuple(top.x+1,top.y,matrix[top.x+1][top.y]));
}
return smallHeap.poll().val; //得到第k小
}
private class Tuple implements Comparable<Tuple>{
int x,y,val;
public Tuple(int x,int y,int val){
this.x = x;
this.y = y;
this.val = val;
}
@Override
public int compareTo(Tuple that){
return this.val - that.val; //从小到大
}
}
48. 旋转图像
matrixnew[col][n−row−1]=matrix[row][col] 插接成水平发转和对角线翻转
public void rotate(int[][] matrix) {
if(matrix==null||matrix[0]==null)return;
int n = matrix.length;
for(int i=0;i<n/2;i++){ //matrix[row][col]水平轴翻转matrix[n−row−1][col]
int[] temp = matrix[i];
matrix[i] = matrix[n-1-i];
matrix[n-1-i] = temp;
}
for(int i=0;i<n;i++){ //matrix[row][col]主对角线翻转matrix[col][row]
for(int j=0;j<i;j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
54. 螺旋矩阵
控制好两个维度,四个变量
public List<Integer> spiralOrder(int[][] matrix) {
if(matrix==null||matrix[0]==null)return null;
int xMax = matrix.length-1, yMax = matrix[0].length-1;
int xMin = 0,yMin = 0;
List<Integer>list = new ArrayList();
while(xMin<=xMax&&yMin<=yMax){
for(int j=yMin;j<=yMax;j++){
list.add(matrix[xMin][j]);
}
xMin++;
for(int i=xMin;i<=xMax;i++){
list.add(matrix[i][yMax]);
}
yMax--;
for(int j=yMax;j>=yMin&&xMin<=xMax;j--){ //防止上面更改了已经不再满足while循环条件
list.add(matrix[xMax][j]);
}
xMax--;
for(int i=xMax;i>=xMin&&yMin<=yMax;i--){ //防止上面更改了已经不再满足while循环条件
list.add(matrix[i][yMin]);
}
yMin++;
}
return list;
}
766. 托普利茨矩阵_
public boolean isToeplitzMatrix(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(matrix[i][j]!=matrix[i-1][j-1]){
return false;
}
}
}
return true;
}
图
210. 课程表 II
核心思想:不断删除入度为0的节点
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
if(numCourses<=0)return new int[0];
HashSet<Integer>[]adj =new HashSet[numCourses];
for(int i=0;i<numCourses;i++){
adj[i] = new HashSet();
}
int[] inDegree = new int[numCourses];
for(int[] coursePair:prerequisites){
adj[coursePair[1]].add(coursePair[0]); //关联coursePair[1]的后驱
inDegree[coursePair[0]]++; //记录coursePair[0]的入度
}
LinkedList<Integer>queue = new LinkedList();
for(int i=0;i<numCourses;i++){
if(inDegree[i]==0)queue.add(i); //初始将所有入度为0的加入
}
int[] res = new int[numCourses];
int cnt = 0;
while(!queue.isEmpty()){
int rmvCourse = queue.poll();
res[cnt] = rmvCourse;
cnt++;
for(int course:adj[rmvCourse]){
//inDegree[course]--; //更新入度
if(--inDegree[course]==0)queue.add(course); //更新入度,为0加入
}
}
//若最终数量不等,则无法完成课程排序
if(cnt==numCourses)return res;
return new int[0];
}
}
易考点补充
LRU
通过map快速判断节点有无 增删时map和双向链表都得变动
class LRUCache {
private HashMap<Integer,Node>map = new HashMap();
private Node head,tail;
private int capacity;
private int size;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
Node node = map.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.val;
}
public void put(int key, int value) {
Node node = map.get(key);
if(node!=null){
node.val = value; //值替换
moveToHead(node);
}else{
node = new Node(key,value);
addToHead(node);
map.put(key,node);
size++;
if(size>capacity){
map.remove(tail.prev.key); // 去除后面的
tail.prev.prev.next = tail;
tail.prev = tail.prev.prev;
size--;
}
}
}
private void moveToHead(Node node){
node.prev.next = node.next;
node.next.prev = node.prev;
addToHead(node);
}
private void addToHead(Node node){
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
class Node{
int key;
int val;
Node prev,next;
public Node(){};
public Node(int key,int val){
this.key = key;
this.val = val;
}
}
}