Skip to content

Latest commit

 

History

History
460 lines (371 loc) · 19 KB

Frequency.md

File metadata and controls

460 lines (371 loc) · 19 KB
  • *代表需要重视的程度
  • 重视的题型:排列组合

Medium

1. 两数之和

求数组内=target的元素下标,HashMap

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

使用boolean[] 256做hash桶,使用双指针,前指针往前移填充hash,发现重复的话(更新结果),后指针前移,擦除hash,循环检查前指针指向的字符是否重复,重复就后指针前移,不重复就前指针前移。

5. **最长的回文子串

DP, 二维DP,填左半边,从下往上,从左往右 子串 dp[i, j] = (char_i == char_j) && dp[i+1, j-1]

for (int i = len-1; i >= 0; i--) {
    for (int j = i+1; j < len; j++) {
        if (s.charAt(i) == s.charAt(j) && (i+1 > j-1 || dp[i+1][j-1])) {
            dp[i][j] = true;
            if (j-i+1 > longest) {
                longest = j - i + 1;
                start = i;
            }
        }
    }
}

6. Z字形变换

Z字形(从上到下,从左到右排列字符串,然后按行收集),关键:控制打印方向 flag

7. 整数反转

直接使用取余即可,不用区分正数负数,负数对10取余,是末尾的数字加个负号(Java是截断除法),判断溢出可以判断在ret*10+last前面判断与MAX_VALUE/10的关系。

8. atoi字符串转整数(未做)

溢出需要全面考虑

9. 回文数

不将整数转化成字符串,按照第7题,将数字反转,看看是否和原数字一样。

***11. 盛最多水的容器

双指针,头尾各一个指针,一开始宽是最长的,所以移动短的那一端,高才有可能增加,进而面积才有可能增加。

12. 13 罗马数字和整型的互相转化(未做)

14. 最长的公共前缀

字符串数组中的最长公共前缀。很简单,索引从0开始到 最短字符串的长度len,然后遍历每个串,求当前索引位置的字符是否都相等,遇到不相等的,直接返回(公共前缀就到此)。

15. 三数之和

固定一个数字,然后就转化成了两数之和问题,可以使用排序+双指针写法;或者使用Hash方法。

16. 最接近的三数之和

双指针,和上题基本一样,固定一个数字,然后头尾双指针移动,双指针移动的方法也一样,只不过就是在内部要更新一下接近值。

*17. 电话号码的字母组合

18. 四数之和(哈希)

三数之和升级版,就是在三数之和的基础之上多加了一层循环,前面循环两个数,然后最后变成两数之和。

19. 删除链表倒数第N个结点(双指针)

一个指针先走N步,然后另一个指针开始走,第一个指针走到头,第二个指针指向的位置就是倒数第N个结点。不用非得

while (fast.next != null) {
    if (n <= 0) slow = slow.next;
    fast = fast.next;
    n--;
}
slow.next = slow.next.next;

20. 有效的括号(栈)

使用栈,遍历当前字符,是)]}时候,pop出栈顶元素,查看是否匹配;是([{括号入栈。

21. 合并两个有序链表(归并)

归并算法

22. 括号生成(回溯、DFS)

生成n对合法的括号字符串(括号匹配),可以组成有效括号重点是:左右括号的数目

非常优秀的一道回溯、DFS类题目,对于排列组合类的回溯,还需多练习。

private void dfs(int n, int l, int r, String cur, List<String> ret) {
    if (cur.length() == 2*n) {
        ret.add(cur);
        return;
    }
    if (l < n) {
        dfs(n, l+1, r, cur+'(', ret);
    }
    if (r < l) {
        dfs(n, l, r+1, cur+')', ret);
    }
}

23. 合并K个链表(归并、堆)

使用大小为K的堆,找到最小的点,链接到结果链表,然后此节点的下一个节点加入堆(此处也是以前没有想到的地方)

***24. 两两交换链表节点(链表)

有一个指向前面的p, 然后q 和 q.next是要交换的两个,但是也要注意p要指向q.next。

public ListNode swapPairsNew(ListNode head) {
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    ListNode p = dummy, q = head;
    while (q != null && q.next != null) {
        p.next = q.next;
        q.next = q.next.next;
        p.next.next = q;
        // 更新 p 和 q
        p = q;
        q = q.next;
    }
    return dummy.next;
}
// 不要和 字符串翻转 代码搞混了
public ListNode swapPairsRecursive(ListNode head) {
    if(head == null || head.next == null) return head;
    ListNode nextNode = head.next;
    head.next = swapPairsRecursive(nextNode.next);
    nextNode.next = head;
    return nextNode;
}

***25. K个一组,反转链表(链表)

// 翻转链表 递归写法
private ListNode reverse2(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode newHead = reverse2(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}

**26. 删除排序数组中的重复项(数组)

数组,去除重复项后,原地修改数组,前面都是非重复的元素,返回长度。 这种重复的要考虑相邻的元素是否相等。

  1. 删除排序数组中的重复项2

***27. 移除元素(数组,多解法)

  1. 要移除的元素多 保留的少,那么就挑选出要保留的元素,从前往后放置
public int removeElement(int[] nums, int val) {
    int ans = 0;
    for(int num: nums) {
        if(num != val) {
            nums[ans] = num;
            ans++;
        }
    }
    return ans;
}
  1. 要移除的元素少 保留的多,那么就将要移除的挪到后面去,要保留的从后往前挪到前面去。
class Solution {
    public int removeElement(int[] nums, int val) {
        int ans = nums.length;
        for (int i = 0; i < ans;) {
            if (nums[i] == val) {
                // 不用找特定的位置再挪,一个一个挪过去就行,也可以循环找到位置
                nums[i] = nums[--ans];
            } else {
                i++;
            }
        }
        return ans;
    }
}

28. strStr()

Sunday算法

29. 两数相除

31. **下一个排列(规则,数组)

数组数字序列,下一个更大的排列。 思路:从后往前找到arr[i-1] < arr[i]的位置,然后找到后面第一个比arr[i-1]大的位置,两个位置交换,然后 i-1往后的元素(从大到小排列的),逆序即可.

33. 搜索旋转排序数组(二分查找)

根据num[l] 与num[m]的关系

相关题目:81

35. 搜索插入位置(二分)

二分查找,注意初始值(和边界值有关),以及如何判断边界值

36. 有效的数独(哈希)

判断一个数独(没有数字的部分使用'.'来填充)是否有效(三种规则行1-9、列1-9,3X3格子1-9)。 解法:使用三个hash表(Hash表使用长度为10的boolean数组),一次遍历即可,特别要注意的是3X3格子的下标转换。

38. 外观数列(数组)

从前往后读前一个字符串连续数目、数字,追加到结果中。 求连续的数目,最好的办法是判断 j == j+1 是否成立.

39. 组合总和(回溯)

一个数组能组成target的所有组合,不要重复,每个数字可以用多次。 典型的回溯,因为要求所有组合!是一个枚举所有情况的过程。

40. 组合总和2(回溯)

在39题的基础之上,每个数字只能使用一次。 难点:如何去重。方案:排序,判断数组中当前(要添加到结果集)的元素和前一个是否是相等的? 回溯中有两个逻辑

41. 缺失的第一个正数

给你一个未排序的整数数组(可能存在负整数),请你找出其中没有出现的最小的正整数。 思路:最小整数是 <= 数组的长度n的,,并且还是正整数,所以就想到了数组的下标,在数组下标身上进行标记。将数组中<=0的数字使用n+1进行替换,然后遍历数组,绝对值<=n的,数组中对应的元素(若为正)换成负数。

**42. 接雨水(双指针)

木桶原理,每一个柱体可以接雨水,由它的左边和右边的最大值的最小值来决定的。我们可以求每一个柱体的这个值,左遍历一遍,右遍历一遍即可。 我们可以使用双指针进行优化, 小的那一边进行移动,然后不断的更新小的那一边的max值,最终结果不断的累加max-cur的。

43. 字符串相乘(大数乘法)

竖式模拟乘法,可以找规律:最终结果最多 len1+len2位;str1[i] * str2[j],获得的局部结果在第ret[i+j]和ret[i+j+1]位置。 难点:如何将这些局部的值加在一起? 就是从后往前计算,在计算每个局部和 sum = str1[i] * str2[j]+ret[i+j+1], ret[i+j+1] = sum % 10; ret[i+j] += sum /10; 注意去除前导0,和结果仅为0的情况。

45. *跳跃游戏2(贪心)

问至少多少次可以跳到末尾,贪心算法,每次跳到所能触及到的最远位置,

46. 全排列(回溯)

求没有重复的数组的去排列。 回溯,还有used数组,套模板即可。

47. 全排列2 (回溯)

和上题一样,但是难点:如何解决重复? 对数组预先排序,对于重复的元素,只使用第一个元素。 if (i > 0 && num[i] == num[i-1] && !used[i-1]) continue; 如果used[i-1]没有被使用,那么后续肯定会被使用,就会导致重复。

48. 旋转图像

原地顺时针旋转90度,左上和右下进行交换,然后再上下交换。

原地修改的算法,一般思路是:交换、做标记(�加正负号、bit位做标记)

49. 字母异位词分组

将排序后的字符串作为识别的key,添加到map的集合中。 如何避免排序,可以自己统计每种字母的情况,然后组成特殊的字符串当成map的key

char[] chars = str.toCharArray();
Arrays.sort(chars);

50. Pow(x, n)

快速幂算法

53. 最大子序和(贪心)

数组最大的子序列的和。思路:遍历,碰见 当前值+sum < 当前值时候,让sum= 当前值

54. 螺旋矩阵(矩阵)

螺旋打印矩阵,控制边界即可。

*55. 跳跃游戏1(贪心)

数组值代表能跳的距离,从第一个位置判断能否走到最后一个位置。每一个位置,都有一个能走的最远位置,判断这个位置是否大于等于当前位置。

56. 合并区间()

排序,前一个区间的s 和 e 关系:(1)e < nextS, 加入结果集 (2) e > nextS, e 与 nextE的关系,去较小值

区间类型的题目,一般都需要排序,然后找相邻区间的关系,或者类似于括号似的,计一下数

57. **插入区间

有序的无重叠区间。插入一个新的区间,使得所有新区间不重叠。 考的是逻辑思路:右区间小于当前新区间的左区间,直接加入结果集,然后是处理重叠部分,最后将剩下的(左区间大于新区间的右区间)加入结果集。

58. 最后一个单词的长度

从后往前遍历,去除前导 空格,�从不是空格的位置开始,再次从后往前遍历,计数,碰见空格停止。

59. 螺旋矩阵2(矩阵)

60. 第K个排列(规律)

阶乘先保存在数组里面 找规律,注意k是从第1个数字开始的,所以K要先减去1, 然后 k / (n-1)! k = k % (n-1)!

61. 旋转数组(链表)

计算链表长度len与k的关系,能整除,不用旋转;否则 找到倒数第k+1个节点和倒数第一个节点,然后改变指针指向即可

62.63 不同的路径1&2(DP)

机器人位于棋盘的左上角,只能往下或者往右走,问到右下角有多少走法。 63题,方格上有障碍物。 解法:DP,按列进行,从上到下,从左往右

64. 最小的路径和(DP)

和62、63一样,只不过是找一条路径,路径和最小,和前面两题思路一样。

66. 加一

初始进位为1,从后往前加这个进位,如果进位==0,break, 查看最终进位是否为0,如果不是,说明结果位数增加。

***67. 二进制相加

使用两个下标,分别从两个串的尾部到头。一开始写的时候特别费劲,总想用一个索引来控制两个串,在下标的转换中遇到了不少问题。 这题和快慢指针,求链表的长度差思想一致。这种对不起的,有距离差的,使用两个指针更方便,更好控制。

69. x 的平方根(二分)

m * m >= x 注意m*m可能会溢出,要进行强制类型转换。

70. 爬楼梯(DP)

dp[n] = dp[n-1] + dp[n-2];

71. 简化路径(栈)

将路径根据”/”split成数组,对于“..”且栈不为空的时候,弹出栈中的元素,其他的不等于"", ".", “..”

***72. 编辑距离(DP)

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 可以对一个单词进行如下三种操作:插入一个字符;删除一个字符;替换一个字符。 DP:

  1. dp[i][j]中存的是什么? word1的前i个字符和word2的前j个字符的编辑距离。

73. 矩阵置零(矩阵)

使用原地算法,将元素为0的数组,行、列全置为0. 如何不使用额外的空间? 使用第一行第一列做标记(所以要注意不能修改第一行和第一列的数据)

74. 搜索二维矩阵(二分、减治)

矩阵的每一行的第一个数大于上一行的最后一个数。很典型的二分法,注意下标的切换。

  1. 搜索二维矩阵2 每一行、每一列都升序。从矩阵的右上角或者左下角,就和二叉搜索树似的,往左是减小,往下是增大,减治的思想。target > matrix[row][col] row++, target < matrix[row][col] col--

**75. 颜色分类

荷兰国旗问题,如果使用一次遍历,就将三类数据分类。分成两类很简单,交换就行了。分成三类,也是需要交换,往两边交换。

难点:细节,交换的时候,指针的变化。

public void sortColors(int[] nums) {
    // p0左侧是0区,p2右侧是2区
    int p0 = 0, p2 = nums.length-1, cur = 0;
    while (cur <= p2) {  // [2,0,1]
        if (nums[cur] == 0) {
            swap(nums, cur++, p0++);
        } else if (nums[cur] == 2) {
            swap(nums, cur, p2--);
        } else {
            cur++;
        }
    }
}

**76. 最小覆盖子串(哈希)

在S串中找到包含T串所有字母的最小子串。

使用int[]作为map,双指针,前指针去减map中的元素(并统计count), 当count==0时候,说明map内的元素都包括在前后指针之间了,此时移动后指针,map中的元素累加,value大于0的时候count++,说明有元素没有包括在前后指针之间。

亮点: 使用数字作为map,使用计数替代contain

77. 组合(回溯)

78. 子集(回溯)

79. 单词搜索(回溯, DFS)

***80. 删除排序数组中的重复项2()

和26题一样的要求,只不过要求排序数组内的元素最多2个。

// i是符合要求的元素,num[i-2]避免出现2次
public int removeDuplicates(int[] nums) {
    int i = 0;
    for (int num : nums) {
        if (i < 2 || num > nums[i-2]) {
            nums[i++] = num;
        }
    }
    return i;
}

81. 搜索旋转排序数组

//  [1,3,1,1,1]   [1,1,1,3,1]
public boolean search(int[] nums, int target) {
    if (nums.length == 0) return false;
    int left = 0, right = nums.length - 1;
    int mid;
    while (left < right) {
        mid = left + (right - left) / 2;
        // if (target == nums[mid]) return true;
        // 和33题不同的地方
        if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
            left++;
            right--;
        } else if (nums[left] <= nums[mid]) { // 左边有序(旋转点在右边)
            if (nums[left] <= target && target <= nums[mid]) right = mid;
            else left = mid + 1;
        } else { // 右边有序(旋转点在左边)
            if (nums[mid] < target && target <= nums[right]) left = mid + 1;
            else right = mid;
        }
    }
    return nums[left] == target;
}

***82. 83. 删除排序链表中的重复元素I/II

  1. 重复元素保留一个(简单,碰见当前和下一个相等,就把下一个隔过去)
if (p.val == p.next.val) {
    p.next = p.next.next;
} else {
    p = p.next;
}
  1. ***一个都不保留(较难) pre, cur, 当cur.val == cur.next.val的时候,cur++, 发现不等时候(或者cur.next == null),判断此时pre.next == cur ? 不等于说明,cur是重复元素,pre.next = cur.next, 否则pre = cur

****84. 柱状图中最大的矩形(单调栈经典)

暴力思维:每一列为高,向左右两边扩散,看看能扩散多远(宽)。扩散的时候,只能找周围连续的、比自己高的列,碰见比自己低的列,就要停下。

优化:这很显然是一个下一个更小问题,就想到了单调递增栈。咱们可以往一边去扩散,以peek为高,往右找,不断的拓宽。碰到递减的元素pop出peek,便可以计算以peek为高的 矩形。还一个要注意的点:w如何求,必须得用当前的w = i - stack.peek -1(stack.isEmpty, w = i)

85. 最大的矩形

解法1:和84题一样的思路,把每一行当成一个84题的柱形图

解法2:每一个点,都求一个h l r, 然后求面积

86. 分隔链表

给定一个target,分隔成比target小的和大于等于target的两个链表,然后再连接起来

87. 扰乱字符串

88. 合并两个有序数组

89. 格雷编码

迭代做法,分两部分: 前一批格雷码 + 前一批倒序(头部补1)

90. 子集2

Hard

4. 寻找两个正序数组的中位数

10. 正则表达式匹配

30. 串联所有单词的子串

32. 最长的有效括号

一个串的最长有效括号子串。

  1. 栈的做法: 栈中保存下标"("入栈, ")"出栈(然后当前下标-栈顶下标作为长度,若栈为空,当前下标入栈,作为分隔点,这也是初始栈中放-1的原因)
  2. DP做法,两大类情况 "()" "))",其余的情况都为0
  • i-1和i为"()"情况 dp[i] = dp[i-2]+2
  • i-1和i为 "))" 要看i-1位置的")" 匹配了多少, 且要看 i-dp[i-1]-1位置是否为 "(" 则 dp[i] = dp[i-1](内部) + 2+ dp[i-dp[i-1]-1-1](外部)
  • 其余为0

37. 解数独(回溯)

难点:如何移动到下一个位置。j每次+1 当j == col的时候,i+1, 当i == row的时候,整个函数返回。

// 判断 3 X 3 下标
x = 3 * (row / 3) + i / 3;
y = 3 * (col / 3) + i % 3;

44. 通配符匹配(DP)

51.52. N皇后(回溯)

求N皇后的方案或者方案数目。

回溯典型题: 按row回溯,每一row有col种放法。 难点,如何剪枝(判断是否有效):同行、同列、主、副对角线

对角线 行 + 列 == 常数 行 - 列 == 常数

65. 有效数字(自动机)

画出状态转移图,使用有限状态自动机很简单。 状态转移,表驱动:将char转换成下标,在二维数组间转移,碰见无效状态、无效操作直接返回。

68. 文本左右对齐

题目大意:每一行单词在指定宽度内对齐,单词间的空格从左到右要均匀分布 考察的就是逻辑和字符串的操作,还有更重要的是,收尾处理(循环内没有处理完,跳出循环了,要在循环外)