本文最后更新于43 天前
Hot 100笔记
源于力扣
1-10
1.两数之和
class Solution {
//思路key存余值,v存索引,比如2 找不到,存7,下次7就找到
public static int[] twoSum(int[] nums, int target) {
int re [] = new int [2];
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<nums.length;i++){
if(!map.containsKey(nums[i])){
map.put(target - nums[i],i);
}else{
// return new int[]{i, j};用这种可以直接创建数组固定
re[0] = i;
re[1] = map.get(nums[i]);
return re;
}
}
return re ;
}
}
2.字母异位词分组
class Solution {
// 思路:排序就能判断是不是一个字符串,用map key是排序后,value是原str里面的数组
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> hm = new HashMap<>();
for (String str : strs) {
char[] charArray = str.toCharArray();
Arrays.sort(charArray);
String newStr = new String(charArray);//这里不能用toString
if (hm.containsKey(newStr)) {
hm.get(newStr).add(str);
} else {
List<String> fenlei = new ArrayList<String>();
fenlei.add(str);
hm.put(newStr, fenlei);
}
}
List<List<String>> re = new ArrayList<List<String>>();
for(Map.Entry<String, List<String>> Entry : hm.entrySet()){
re.add(Entry.getValue());
}
return re;
}
}
3.最长连续序列
public class Code3 {
// 每次去找是否存在x - 1 ,直到不存在那么这个x就是某个序列的最小值
// Set进行去重,遍历找到每个序列的最小值,然后反向找长度
public int longestConsecutive(int[] nums) {
if (nums.length < 1) return nums.length;
int ans = 0;
HashSet<Integer> set = new HashSet<Integer>();
for (int num : nums) {
set.add(num);
}
for (Integer integer : set) {
if (set.contains(integer - 1)) {
continue;
}
// 找到某个序列的最小值
int y = integer;
while (set.contains(y)) {
y++;
}
// 找到某个序列的最大值此时是y-1 因此距离(y - 1) - integer + 1
ans = Math.max(ans, y - integer);
//
}
return ans;
}
}
4.移动零
public class Code4 {
// 找出所有不等于0的个数,然后直接替换,然后剩下补0
public void moveZeroes(int[] nums) {
int count = 0;
for (int i = 0; i < nums.length;i++){
if (nums[i] != 0 ) {
nums[count++] = nums[i];
}
}
for (int i = count; i < nums.length;i++){
nums[i] = 0;
}
}
}
5.盛最多水的容器
public class Code5 {
public int maxArea(int[] height) {
//头尾指针,优先移动小的,因为面积想要找更大只能移动最小值
int l = 0, r = height.length - 1;
int ans = 0;
while (l < r) {
ans = Math.max(ans,(r-l) * Math.min(height[l], height[r]));
if (height[l] < height[r]) {
l++;
} else {
r--;
}
}
return ans;
}
}
6.三数之和
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//排序加双指针加去指针去重
Arrays.sort(nums);
ArrayList<List<Integer>> re = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
//第一次去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int l = i + 1, r = nums.length - 1;
int target = -nums[i];
while (l < r) {
if (nums[l] + nums[r] == target) {
re.add(Arrays.asList(nums[l], nums[r], -target));
while (l < r && nums[l] == nums[l + 1])//先跳过重复元素再移动指针
l++;
while (l < r && nums[r] == nums[r - 1])
r--;
l++;
r--;
//还要去重
} else if (nums[l] + nums[r] > target)
r--;
else
l++;
}
}
return re;
}
}
7.接雨水
class Solution {
public int trap(int[] height) {
int n = height.length;
//前缀和,计算从左到右的和从右到左边的前缀和
//注意遍历的边界,初始化,以及最大值替换都很恶心
int[] prel = new int[n];
int[] prer = new int[n];
prel[0] = height[0];
prer[n-1] = height[n - 1];
int max = height[0];
for (int i = 1; i < n; i++) {
max = Math.max(height[i], max);//更新max后赋值
prel[i]= max ;
}
max = height[n - 1];
for (int i = n - 2; i > 0; i--) {
max = Math.max(max, height[i]);
prer[i]= max ;
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += Math.max(Math.min(prel[i], prer[i]) - height[i],0);
}
return ans;
}
}
public int trap2(int[] height) {
//思路二:双指针,维护左侧和右侧的最大值,并且移动小的指针,
// 由是对于小的是确定的,另一边是未知的,但是可以提取出至少大于某个高度
int n = height.length;
if (n == 0) return 0; // 处理空数组的情况
int ans = 0;
int leftMax = height[0];
int rightMax = height[n - 1];
int l = 1, r = n - 2;
while (l <= r) {
if (leftMax <= rightMax) {//处理左侧
if (height[l] < leftMax) {//是否超过左边最大值
ans += leftMax - height[l];
} else {//更新最大值
leftMax = height[l];
}
l++;
} else {
if (height[r] <= rightMax) {
ans += rightMax - height[r];
} else {
rightMax = height[r];
}
r--;
}
}
return ans;
}
8.无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
// 用哈希去重,哈希表数组,set,map都可以,维护好左指针为上一个重复字符的下一位且不回退
HashMap<Character, Integer> map = new HashMap<>();
int max = 0, start = 0;
for (int end = 0; end < s.length(); end++) {
char ch = s.charAt(end);
if (map.containsKey(ch)){
//跳到上一个出现的地方取start最大值不能让左指针回退
if(start <= map.get(ch)+1){
start = map.get(ch)+1;
}
}
max = Math.max(max,end - start + 1);
map.put(ch,end);
}
return max;
}
}
9.找到字符串中所有字母异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
//定长滑动窗口,计数用两个数组,对比数组数量字母- ’a‘用equals
int[] counts = new int[26];
int[] countp = new int[26];
List<Integer> result = new ArrayList<Integer>();
if(s.length()< p.length()){
return result;
}
//初始化窗口计数
for (int i = 0; i < p.length(); i++) {
countp[p.charAt(i) - 'a']++;
}
//定长思路,初始化第一个窗口,先进后出
for (int i = 0; i < p.length(); i++) {
counts[s.charAt(i) - 'a']++;
}
if (Arrays.equals(countp, counts)) {
result.add(0);
}
for (int i = p.length(); i < s.length(); i++) {
counts[s.charAt(i) - 'a'] ++;
counts[s.charAt(i - p.length()) - 'a'] --;
if (Arrays.equals(countp, counts)) {
result.add(i - p.length() +1 );//最终记录的是起始点!
}
}
return result;
}
}
10.和为K的子数组
public class Code10 {
public int subarraySum(int[] nums, int k) {
// 思路一:由于是连续,可以直接两个for跑
// 思路二:区间和等于前缀和减去某个位置的前缀和
// 拓展:类似两数之和,利用哈希找对应的数,寻找a - b = k
int res = 0, n = nums.length;
int[] pre = new int[n + 1];
for (int i = 0; i < nums.length; i++) {
pre[i + 1] = nums[i] + pre[i];
}
for (int i = 0; i < pre.length; i++) {
for (int j = i + 1; j < pre.length; j++) {
if (pre[j] - pre[i] == k) {
res++;
}
}
}
return res;
}
public static int subarraySum2(int[] nums, int k) {
int res = 0, n = nums.length;
int[] pre = new int[n + 1];
for (int i = 0; i < nums.length; i++) {
pre[i + 1] = nums[i] + pre[i];
}
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
// 寻找a - b = k,就是相当于pre[j]-之前某个pre[i] = 成立
map.put(0, 1);//要保证1 - 0 = 1 有零可以找到
for (int i = 1; i < pre.length; i++) {
if (map.containsKey(pre[i] - k)) {
res += map.get(pre[i] - k);
}
map.put(pre[i], map.getOrDefault(pre[i], 0) + 1);
}
return res;
}
}
11-20
11.滑动窗口最大值
public class Code10 {
public int[] maxSlidingWindow(int[] nums, int k) {
// 单调队列:维护某个区间的最大值,存下标
// 思路是入的时候弹出比他小的,如果遍历值超过k范围就出, 弹的是坐标
int n = nums.length;
int[] re = new int[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 入
while (!deque.isEmpty() && nums[i] >= nums[deque.getLast()]) {
deque.removeLast();
}
deque.add(i);
// 出
if (i- deque.getFirst() >=k) {
deque.removeFirst();
}
// 记录答案
if (i >= k-1 ) {
re[i - k + 1] = nums[deque.getFirst()];
}
}
return re;
}
}
12.最小覆盖子串
class Solution {
public String minWindow(String s, String t) {
//双指针,覆盖函数自己写,维护两个单词计数数组
int[] cntT = new int[128];
int[] cntS = new int[128];
int n = s.length();
int m = t.length();
if(n < m )return "";
for (int i = 0; i < m; i++) {
cntT[t.charAt(i)]++;
}
int l = 0,ll = -1 ,rr = n;
for (int r = 0; r < n; r++) {
cntS[s.charAt(r)]++;
//如果当前字串覆盖大于覆盖串,就开始收缩
while (isCovered(cntT,cntS)) {
//由于返回需要记录字符串,这里需要记录最小的左右指针
if(rr - ll >r -l){
rr= r;
ll = l;
}
cntS[s.charAt(l)]--;
l++;
}
}
return ll == -1 ? "":s.substring(ll,rr+1);
}
private boolean isCovered(int[] cntT, int[] cntS) {
for (int i = 0; i < 128; i++) {
if (cntS[i] < cntT[i]) {
return false;
}
}
return true;
}
}
13.最大子数组和
public static int dp(int[] nums) {//[-2,1,-3,4,-1,2,1,-5,4] [4,-1,2,1] 的和最大,输出6
// 思路动态规划:dp[i]表示0-i的最大子数组和,那么对于新的数,要么加入和dp[i-1],要么自己做头等于dp[i]
int n = nums.length; // -2,1,-2,4,3,5,6,1,5
int[] dp = new int[n];
dp[0] = nums[0];
int ans = dp[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], 0) + nums[i];
ans = Math.max(dp[i], ans);
}
return ans;
}
public static int preSum(int[] nums) {//[-2,1,-3,4,-1,2,1,-5,4] [4,-1,2,1] 的和最大,输出6
// 思路前缀和:子数组和等于两个前缀和相减,维护当前前缀和减去当前最小前缀和
int n = nums.length; // -2,-1,-4,0,-1,1,2,-3,1 2-(-4) = 6
int[] pre = new int[n];
pre[0] = nums[0];
int min = Math.min(pre[0], 0), res = pre[0];
for (int i = 1; i < nums.length; i++) {
pre[i] = pre[i - 1] + nums[i];
res = Math.max(res, pre[i] - min);
min = Math.min(min, pre[i]);
}
return res;
}
14.合并区间
public static int[][] merge(int[][] intervals) {
//根据左端点排序,然后判断区间右端点是否大于下一个区间的左端点,需要熟悉api,排序,集合转数组,获取集合最后一个
//先把第一个丢进结果数组,然后进一个判断一个
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
List<int[]> merged = new ArrayList<>();
merged.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
if(merged.get(merged.size()-1)[1]>=intervals[i][0]){
merged.get(merged.size()-1)[1]=Math.max(merged.get(merged.size()-1)[1],intervals[i][1]);
}else{
merged.add(intervals[i]);
}
}
return merged.toArray(new int[merged.size()][]);
}
15.轮转数组
public class Code14 {
public static void rotate1(int[] nums, int k) {
//复制数组,然后平移
int n = nums.length;
int[] tmp = Arrays.copyOf(nums, n);
for (int i = 0; i < n; i++) {
int i1 = (i + k) % n;
nums[i1] = tmp[i];
}
}
public static void rotate2(int[] nums, int k) {
//构造一个反转的函数,传入对应的下表,那么变成对应的需要反转三次
int n = nums.length;
k %= n;
rotate(nums, 0, n - 1);
rotate(nums, 0, k - 1);
rotate(nums, k, n - 1);
}
public static void rotate(int[] nums, int start, int end) {
//7 6 5 4 3 2 1 567 1234
int n = nums.length;
while (start <= end && start >= 0) {
int tmp = nums[start];
nums[start] = nums[end];
nums[end] = tmp;
start++;
end--;
}
}
}
16.除自身以外数组的乘积
class Solution {
public static int[] productExceptSelf(int[] nums) {//1 2 3 4
//思路,用两个数组分别左前缀和和后缀和,然后可以计算0~x-1 和x+1~i 的乘积了
int n = nums.length;
int[] pre = new int[n];
int[] post = new int[n];
pre[0] = 1;//记得两个都要错位,得到的是左边的数据,也就是多一个无论第一个多少第一个都一次1
post[n-1] = 1;
for (int i = 1; i < nums.length; i++) { // 1 1 2 6
pre[i] = pre[i - 1] * nums[i-1];
post[n - i - 1] = post[n - i] * nums[n -i];
}
int[] re = new int[n];
for (int i = 0; i < re.length; i++) {
re[i] = pre[i] * post[i];
}
return re;
}
}
17.缺失的第一个正数
public class Code16 {
public static int firstMissingPositive(int[] nums) {
// 思路:把他们交换到下标+1上,然后从头遍历就知道谁没来
int n = nums.length;
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
// 交换 nums[i] 和 nums[nums[i] - 1] //就是当前值是否是当前值对应的下标位置
int temp = nums[i];
nums[i] = nums[nums[i] - 1];
nums[temp - 1] = temp;
}
}
for (int i = 0; i < n; i++) {
if (nums[i] - 1 != i) {
return i + 1;//遍历数据
}
}
return n + 1;
}
}
18.螺旋矩阵
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
//先计算出下一个要走的位置,然后进行标记,用数组代表移动方向
int m = matrix.length;
int n = matrix[0].length;
int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};//不同下标可以表示转向
List<Integer> ans = new ArrayList<>(m * n);
int i = 0, j = 0, di = 0;
for (int k = 0; k < m * n; k++) {
ans.add(matrix[i][j]);
matrix[i][j] = Integer.MAX_VALUE;
// 计算下一个坐标
int x = i + dirs[di][0];
int y = j + dirs[di][1];
if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] == Integer.MAX_VALUE) {
di = (di + 1) % 4; // 右转 90°
}
i +=dirs[di][0];
j +=dirs[di][1];
}
return ans;
}
}
19.旋转图像
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
int[][] matrix_new = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix[i][j] = matrix_new[i][j];
}
}
}
}
20.搜索二维矩阵II
public class Code20 {
public boolean searchMatrix(int[][] matrix, int target) {
//右上角作为一个二叉搜索树,或者二分,或者排除法
int i = 0;
int j = matrix[0].length - 1; // 从右上角开始
while (j >= 0 && i < matrix.length) {
if (matrix[i][j] == target) {
return true; // 找到 target
}
if (matrix[i][j] > target) {
j--;
} else {
i++;
}
}
return false;
}
}
21-30
21.相交链表
public class Code21 {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 快慢指针
ListNode pa;
ListNode pb;
pa = headA;
pb = headB;
while(pa!=pb){
pa = pa ==null? pb : pa.next;
pb = pb ==null? pa : pb.next;
}
return pa;
}
}
22.反转链表
public class Code22 {
public ListNode reverseList(ListNode head) {
// 遍历赋值,然后需要存储前面的节点,把指针弄给前面的节点
ListNode prev = null;
ListNode cur;
cur = head;
while(cur != null) {
ListNode node = cur.next;
cur.next = prev;
prev = cur;
cur = node.next;
}
return prev;
}
}
23.回文链表
public class Code23 {
public boolean isPalindrome(ListNode head) {
//最快的思路就是数组+头尾指针
//新思路: 快慢指针获取中间,反转后面链表,然后反过来双边遍历
ListNode slow = head;
ListNode fast = head;
while (fast.next.next != null && slow.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode cur = slow;
ListNode prev = null;
while (cur != null) {
ListNode node = cur.next;
cur.next = prev;
prev = cur;
cur = node;
}
ListNode p = head;
while (p != null && prev != null) {
if (p.val != prev.val) {
return false;
}
p = p.next;
prev = prev.next;
}
return true;
}
}
24.环形链表
public class Solution {
//快慢指针,两个从头跑的话注意while条件只要操作fast, 并且判断的是节点地址是否相同而不是值
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
25.环形链表II
public class Code25 {
public ListNode detectCycle(ListNode head) {
// 快慢指针,然后在相遇地方让头节点和满节点一起跑相遇,证明方法可以去找
ListNode slow = head;
ListNode fast = head;
while(fast!=null && fast.next!=null&&slow.next!=null){
fast=fast.next.next;
slow = slow.next;
if(fast.val == slow.val){
while(slow!=head){
slow = slow.next;
head = head.next;
}
}
}
return slow;
}
}
26.合并两个有序链表
public class Code27 {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//直接把两个数再加上进位数,注意更新记位数
int carry = 0;
ListNode node = new ListNode(0);//虚拟节点
ListNode cur = node;
while (l1 != null || l2 != null || carry != 0) {
int sum = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + carry;
node.next = new ListNode(sum % 10);
node = node.next;
carry = sum / 10;
if( l1 != null){
l1 = l1.next;
}
if( l2 != null){
l2 = l2.next;
}
}
return cur.next;
}
}
27.两数相加
public class Code27 {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//直接把两个数再加上进位数,注意更新记位数
int carry = 0;
ListNode node = new ListNode(0);//虚拟节点
ListNode cur = node;
while (l1 != null || l2 != null || carry != 0) {
int sum = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + carry;
node.next = new ListNode(sum % 10);
node = node.next;
carry = sum / 10;
if( l1 != null){
l1 = l1.next;
}
if( l2 != null){
l2 = l2.next;
}
}
return cur.next;
}
}
28.删除链表的倒数第N个节点
public class Code28 {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 可以反转链表,然后遍历计数删除
// 虚拟节点 + 快慢指针,让快指针先走n步,用虚拟节点可以遍历到达的地方直接next替换
ListNode re = new ListNode(0);
re.next = head;
ListNode fast = re;
ListNode slow = re;
while(n-->0){
fast = fast.next;
}
while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return re.next;
}
}
29.两两交换链表中的节点
public class Code29 {
public ListNode swapPairs(ListNode head) {
// 虚拟节点开始,并且每次都交换其后面的两个节点,需要三次操作
ListNode re = new ListNode(0);
re.next = head;
ListNode cur = re;
while (cur.next!=null && cur.next.next !=null) {
ListNode tmp1 = cur.next;
ListNode tmp2 = cur.next.next;
cur.next = tmp2;
tmp1.next = tmp2.next;
tmp2.next = tmp1;
cur = cur.next.next;
}
return re.next;
}
}
30.K个一组翻转链表
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 可以分组,分组后进行反转的操作,恶心在细节处理
if(head == null || k == 1){
return head;
}
ListNode re = new ListNode(0);
re.next = head;
ListNode preGroup = re; //记录每一组的前缀节点
while (true) {
//确定好对应的开始和结束节点
ListNode start = preGroup.next;
ListNode end = start;
for (int i = 0; i < k - 1 && end != null; i++) {
end = end.next;
}
if(end==null) break; //不足k个节点就结束
// 记录下一组的开始节点
ListNode nextStart = end.next;
end.next = null;//断开和一下组的链接
preGroup.next = reverse(start);
//反转后头节点是尾
start.next = nextStart;
preGroup = start;
}
return re.next;
}
//反转
private ListNode reverse(ListNode head) {
ListNode prev = null;
ListNode cur = head, next;
while (cur != null) {
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
}
31-40
31.随机链表的复制
public class Code31 {
HashMap<Node,Node> map = new HashMap<>();
public Node copyRandomList(Node head) {
// 回溯创建,利用哈希表去记录当前节点和新节点的关系
if(head ==null){
return null;
}
if(!map.containsKey(head)){
Node headNew = new Node(head.val);
map.put(head,headNew);
headNew.next = copyRandomList(head.next);
headNew.random = copyRandomList(head.random);
}
return map.get(head);
}
}
32.排序链表
public class Code32 {
public ListNode sortList(ListNode head) {
// 分治法归并排序,注意找中点(快慢指针),合并链表,递归操作
if (head == null || head.next == null) {
return head;
}
ListNode mid = middleNode(head);
mid = sortList(mid);
head = sortList(head);
return mergeTwoLists(head, mid);
}
//找中点,注意找到后要断开和后面的联系,引入一个slow的前驱节点
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
ListNode pre = head;
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
pre.next = null;//断开链接
return slow;
}
//合并链表(哨兵机制)
private ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode re = new ListNode(0);
ListNode cur = re;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 == null ? list2 : list1;
return re.next;
}
}
33.合并K个升序链表
public class Code33 {
public ListNode mergeKLists(ListNode[] lists) {
//分治递归,类似归并排序,用m来多一个操作变量,注意ij的操作,然后合并链表
return merges(lists, 0, lists.length);
}
private ListNode merges(ListNode[] list, int i, int j) {
int m = j - i;
if (m == 0) {
return null;
}
if (m == 1) {
return list[i];
}
ListNode left = merges(list, i, i + m / 2);
ListNode right = merges(list, i + m / 2, j);
return merge(left, right);
}
private ListNode merge(ListNode list1, ListNode list2) {
ListNode re = new ListNode(0);
ListNode cur = re;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return re.next;
}
}
34.LRU缓存
public class Code34 {
// 利用一个双向链表LinkedHashMap, Code34就是LRU对象,用好api,比如迭代器,remove等操作
private final int capacity;
private final Map<Integer, Integer> map = new LinkedHashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
Integer remove = map.remove(key);
if(remove ==null){
return -1 ;
}
map.put(key, remove);
return remove;
}
public void put(int key, int value) {
if (map.containsKey(key)) {//如果存在key,就去删除,然后添加到链表末尾
map.remove(key);
map.put(key,value);
return;
}else{//如果不存在,判断是否已经满了,获取队头
if(map.size()==capacity){
Integer first = map.keySet().iterator().next();//用迭代器去获取第一个元素
map.remove(first);
}
}
map.put(key,value);
}
}
35.二叉树的中序遍历
public class Code35 {
//递归中序遍历,dfs解决
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(res, root);
return res;
}
public void dfs(List<Integer> res, TreeNode root) {
if (root == null) return;
dfs(res, root.left);
res.add(root.val);
dfs(res, root.right);
}
}
36.二叉树的最大深度
public class Code36 {
//注意节点空返回的是0,左右递归找最大值就行
public int maxDepth(TreeNode root) {
return max(root);
}
public int max(TreeNode root) {
if (root == null) {
return 0;
}
int lmax = max(root.left);
int rmax = max(root.right);
return Math.max(lmax, rmax) + 1;
}
}
37.翻转二叉树
public class Code37 {
public TreeNode invertTree(TreeNode root) {
//递归,先反转,在递归
if (root == null) {
return null;
}
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
38.对称二叉树
class Solution {
public boolean isSymmetric(TreeNode root) {
//递归判断其中有个是null,那么另一边不是null就是错的,递归是左左对右右,左右对右左
return isSameTree(root.left,root.right);
}
public boolean isSameTree(TreeNode left, TreeNode right) {
if (left==null|| right==null) return left==right;
if(left.val !=right.val){
return false;
}
return isSameTree(left.left, right.right)&&isSameTree(right.left, left.right);
}
}
39.二叉树的直径
public class Code39 {
int ans;
// 怕绕住建议把最终答案写在成员变量里面
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return ans -2 ;//减去开始的两高度,其实也可以在下面返回-1
}
public int dfs(TreeNode root) {
if (root == null) return 0;
int lmax = dfs(root.left) + 1;
int rmax = dfs(root.right) + 1;
ans = Math.max(ans, lmax + rmax);//相当于左子树深度加右子树深度
return Math.max(lmax, rmax); //需要递归返回的是哪一边的树大用哪个
}
}
40.二叉树的层次遍历
public List<List<Integer>> levelOrder(TreeNode root) {
//bfs借助队列Queue(Deque)的长度,对队列里面的弹出进行添加最终的答案,然后添加左右节点
List<List<Integer>> re = new ArrayList<>();
if(root==null) return re;
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()) {
ArrayList<Integer> list = new ArrayList<>();
int n = queue.size();
while(n-->0){
TreeNode poll = queue.poll();
list.add(poll.val);
if(poll.left!= null) queue.add(poll.left);
if(poll.right!= null) queue.add(poll.right);
}
re.add(list);
}
return re;
}
41-50
41.将有序数组转换为二叉搜索树
class Code41 {
public TreeNode sortedArrayToBST(int[] nums) {
return dfs(nums, 0, nums.length);
}
public static TreeNode dfs(int[] nums, int start, int end) {
if (start == end) {
return null;//如果相等就证明没有节点可以创建那就是null
}
int m = (start + end) / 2;
TreeNode treeNode = new TreeNode(nums[m]);
treeNode.left = dfs(nums, start, m);
treeNode.right = dfs(nums, m + 1, end);
return treeNode;
}
}
42.验证二叉搜索树
class Solution {
private long pre = Long.MIN_VALUE;//这里必须用long,因为int如果他是最大那么就找不到比他大的
public boolean isValidBST(TreeNode root) {
//直接走一个中序遍历,并且保证左小于中小于右
//先递归左下角,然后更新pre,然后递归回去就是判断当前点大于上一个成立
if (root == null) return true;
if (!isValidBST(root.left)) {
return false;
}
if (root.val <= pre) {
return false;
}
pre = root.val;
return isValidBST(root.right);
}
}
43.二叉搜索树中第K小的元素
public class Code43 {
private int k;
public int kthSmallest(TreeNode root, int k) {
// 中序遍历起来,然后第k个就是答案Z
this.k = k;
return dfs(root);
}
public int dfs(TreeNode root) {
if (root == null) {
return -1;
}
int leftRes = dfs(root.left);
if (leftRes != -1) {
return leftRes;
}
k--;//要在这里减
if (k == 0) {
return root.val;
}
return dfs(root.right);
}
}
44.二叉树的右视图
public class Code44 {
public List<Integer> rightSideView(TreeNode root) {
// 借助队列层次遍历,记录每次出队的最后一个数
List<Integer> re = new ArrayList<>();
if (root == null) {
return re;
}
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()) {//判断队列非空
int n = queue.size();
while (n-- > 0) {
TreeNode poll = queue.poll();
if (n == 0) {
re.add(poll.val);
}
if (poll.left != null) queue.add(poll.left);//这里别写成root
if (poll.right != null) queue.add(poll.right);
}
}
return re;
}
}
45.二叉树展开为链表
public class Code45 {
private TreeNode pre;
public void flatten(TreeNode root) {
// 来个后序遍历,类似反转链表一样有个前置节点就行
if (root==null){
return ;
}
flatten(root.right);
flatten(root.left);
root.left = null; //这里一定要清空左边的指针,理解成处理中间的数据后清空左指针
root.right = pre;
pre = root;
}
}
46.从前序和中序遍历序列构造二叉树
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//用哈希表进行映射,然后根据前序头节点去中序里面找,然后不断往下递归,然后回溯赋值
// Arrays.copyOfRange注意这个赋值的数组是左闭右开
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
int n = preorder.length;
if (n == 0) { // 空节点
return null;
}
//找左子树的大小
Integer index = map.get(preorder[0]);
int[] pre1 = Arrays.copyOfRange(preorder, 1, index); //这里切分的数组要不带头元素
int[] pre2 = Arrays.copyOfRange(preorder, index + 1, n);
int[] in1 = Arrays.copyOfRange(inorder, 0, index+1); //这里切分的数组不带中元素
int[] in2 = Arrays.copyOfRange(inorder, index + 1, n);
TreeNode node = new TreeNode();
node.left = buildTree(pre1, in1);
node.right = buildTree(pre2,in2);
return node;
}
}
47.路径总和III
class Solution {
int res;
HashMap<Long, Integer> map = new HashMap<Long, Integer>();
public int pathSum(TreeNode root, int targetSum) {
//前缀和相减 dfs+哈希(记录路径上的总和的次数,需要添加0) ,采用前缀和的操作往下递归,回溯的时候要恢复现场,
map.put(0L,1);
dfs(root, targetSum, 0L);
return res;
}
public void dfs(TreeNode root, int targetSum, Long sum) {
if(root ==null){
return ;
}
sum += root.val;
if (map.containsKey(sum - targetSum)) {//该路径前面存在某一段和为所求
res += map.get(sum - targetSum);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
if (root.left != null) dfs(root.left, targetSum, sum);
if (root.right != null) dfs(root.right, targetSum, sum);
//回溯
map.put(sum, map.getOrDefault(sum, 0) - 1);
}
}
48.二叉树的最近公共祖先
// 往下递归,并且往上返回,左边找不到就返回右边 , 右边同理
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left==null) return right;
if(right==null) return left;
return root;
}
49.二叉树中的最大路径和
public class Code49 {
private int ans = Integer.MIN_VALUE;
//思路,两边的最大值再对当前节点进行分析
public int maxPathSum(TreeNode root) {
dfs(root);
return ans;
}
public int dfs(TreeNode root) {
if (root == null) {
return 0;
}
int lmax = dfs(root.left);
int rmax = dfs(root.right);
ans = Math.max(ans, lmax + rmax + root.val);//记录两边的最大值和加上节点
return Math.max(0 , Math.max(lmax,rmax) + root.val);//返回两边其中一边的最大值
}
}
50.岛屿数量
public class Code50 {
private boolean[][] flag;
public int numIslands(char[][] grid) {
//遍历,然后四个方向dfs,设计标志数组
int m = grid.length;
int n = grid[0].length;
flag = new boolean[m][n];
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1' && !flag[i][j]) {
dfs(grid, i, j);
ans++;
}
}
}
return ans;
}
public void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] != '1' || flag[i][j]) {
return;
}
flag[i][j] = true;//四个方向
dfs(grid, i - 1, j);
dfs(grid, i, j - 1);
dfs(grid, i + 1, j);
dfs(grid, i, j + 1);
}
}
51-60
51.腐烂的橘子
class Solution {
private int[][] Dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
public int orangesRotting(int[][] grid) {
//bfs,拿个队列来存入腐烂的橘子,然后遍历里面的添加新的,
//开局统计有多少个新鲜橘子,来判断是否还要遍历
ArrayList<int[]> q = new ArrayList<int[]>();
int fresh = 0;
//统计新鲜的
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
fresh++;
} else if (grid[i][j] == 2) {
q.add(new int[]{i, j}); // 一开始可能也有烂橘子
}
}
}
int ans = 0;
while (!q.isEmpty() && fresh > 0) {
ans++;
List<int[]> tmp = q;//防止下面添加导致无限循环
q = new ArrayList<int[]>();
for (int[] ints : tmp) {
for (int[] dir : Dirs) {
int x = ints[0] + dir[0];
int y = ints[1] + dir[1];
if (x >= 0 && y >= 0 && x < m && y < n &&grid[x][y] ==1 ){
fresh--;
grid[x][y] = 2;
q.add(new int[]{x, y});
}
}
}
}
return fresh>0?-1:ans;
}
}
52.课程表
public class Code52 {
public boolean canFinish(int numCourses, int[][] prerequisites) {
//邻接表的操作,然后进行dfs,进行三个状态的判断是否有环
int[] flags = new int[numCourses];//1表示正在访问,-1表示已经来过,0表示没来
List<List<Integer>> list = new ArrayList<List<Integer>>();
for (int i = 0; i < numCourses; i++) {
list.add(new ArrayList<>());
}
for (int[] prerequisite : prerequisites) {
list.get(prerequisite[1]).add(prerequisite[0]);
}
for (int i = 0; i < numCourses; i++) {
if (!dfs(list, flags, i)) {
return false;
}
}
return true;
}
public boolean dfs(List<List<Integer>> list, int[] flags, int i) {
if (flags[i] == 1) {
return false;
}
if (flags[i] == -1) {
return true;
}
flags[i] = 1;
for (Integer j : list.get(i)) {
if (!dfs(list, flags, j)) {
return false;
}
}
flags[i] = -1;
return true;
}
}
53.实现前缀树
class Node {
Node[] sons = new Node[26];
boolean end;
}
public class Code53 {
//来个二十六叉树,然后节点存储对应的是否是截止,然后用当前指针的来移动节点
private Node root;
public Code53() {
root = new Node();
}
public void insert(String word) {
Node cur = root;
for (char c : word.toCharArray()) {
c -= 'a';
if (cur.sons[c] == null) {
cur.sons[c] = new Node();
}
cur = cur.sons[c];
}
cur.end = true;
}
public boolean search(String word) {
Node cur = root;
for (char c : word.toCharArray()) {
c -= 'a';
if(cur.sons[c] ==null){
return false;
}
cur = cur.sons[c];
}
return cur.end;
}
public boolean startsWith(String prefix) {
Node cur = root;
for (char c : prefix.toCharArray()) {
c -= 'a';
if(cur.sons[c] ==null){
return false;
}
cur = cur.sons[c];
}
return true;
}
}
54.全排列
public class Code54 {
public List<List<Integer>> permute(int[] nums) {
// 全排列就是回溯, 每次需要判断是非达到了n次,没有就往下遍历,并且注意item是不断会变的,需要重新创建 n!复杂度
//判断是否来过的数组
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
boolean[] used = new boolean[nums.length];
dfs(0, nums, res, path, used);
return res;
}
public void dfs(int i, int[] nums, List<List<Integer>> res, List<Integer> path, boolean[] used) {
if (i == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int j = 0; j < nums.length; j++) {
if (!used[j]) {
used[j] = true;
path.add(i , nums[j]);
dfs(i+1, nums, res, path, used);
used[j] = false;
path.remove(i);
}
}
}
}
55.子集
public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(0, path, nums, result);
return result;
}
public static void dfs(int i, List<Integer> path, int[] nums, List<List<Integer>> res) {
res.add(new ArrayList<>(path));
for (int j = i; j < nums.length; j++) {
path.add(nums[j]);
dfs(j + 1, path, nums, res);
path.remove(path.size() - 1);
}
}
56.电话号码的字母组合
public class Code56 {
private static final String[] MAPPING = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
char[] digits;
//回溯,这里用数组来作为遍历路径,并且做好数和字母的映射,然后dfs里面就是遍历
public List<String> letterCombinations(String digits) {
this.digits = digits.toCharArray();
int n = digits.length();
List<String> res = new ArrayList<>();
char[] path = new char[n];
if (n == 0) return res;
dfs(0, path, res);
return res;
}
public void dfs(int i, char[] path, List<String> res) {
if (i == digits.length) {
res.add(new String(path));
return;
}
for (char c : MAPPING[digits[i] - '0'].toCharArray()) {
path[i] = c;
dfs(i + 1, path, res);
path[i] = '\u0000';
}
}
}
57.组合总和
//回溯,选和不选的操作,递归的结束是值小于0或者到达最大可选
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
dfs(0, target, path, res, candidates);
return res;
}
public void dfs(int i, int left, List<Integer> path, List<List<Integer>> res, int[] candidates) {
if (left == 0) {
res.add(new ArrayList<>(path));
return;
}
if (left < 0 || i == candidates.length) {
return;
}
dfs(i + 1, left, path, res, candidates); //不选上
path.add(candidates[i]);//选上了,这里可以重复选所以还是i
dfs(i, left - candidates[i], path, res, candidates);
path.remove(path.size() - 1);
}
58.括号生成
public class Code58 {
public List<String> generateParenthesis(int n) {
// dfs,记录左括号必须大于右括号,用数组来存,因此dfs传递括号和左括号数量
List<String> res = new ArrayList<String>();
char[] path = new char[n * 2];
dfs(0, 0, res, path, n);
return res;
}
public void dfs(int i, int left, List<String> res, char[] path, int n) {
if (i == n * 2) {
res.add(new String(path));
return;
}
//填写左括号
if (left < n) {
path[i] = '(';
dfs(i + 1, left + 1, res, path, n);
}
//填写右括号
if (left > i - left) {
path[i] = ')';
dfs(i + 1, left, res, path, n);
}
}
}
59.单词搜索
public class Code59 {
private static final int[][] DIR = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
public static boolean exist(char[][] board, String word) {
// 来个dfs遍历四个方向,并且对当前遍历的元素进行判断,注意记录法访问路径和递归正确结束
boolean[][] path = new boolean[board.length][board[0].length];
char[] w = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (dfs(0, i, j, board, w, path)) {
return true;
}
}
}
return false;
}
public static boolean dfs(int count, int i, int j, char[][] board, char[] c, boolean[][] path) {
if (count == c.length) { // 匹配成功!
return true;
}
if (0 > i || i >= board.length || 0 > j || j >= board[i].length) {
return false;
}
if (board[i][j] != c[count] || path[i][j]) {
return false;
}
path[i][j] = true;
for (int[] ints : DIR) {
int x = i + ints[0];
int y = j + ints[1];
if (dfs(count + 1, x, y, board, c, path)) {
return true;
}
}
path[i][j] = false;
return false;
}
}
60.分割回文串
class Solution {
private String s;
private List<List<String>> res = new ArrayList<List<String>>();
private final List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
//回溯,并且把他想成时带逗号的a,a,b 那么dfs就是去找起始字符串位置和逗号的位置, 记录路径,想象成树型结构
this.s = s;
dfs(0);
return res;
}
//i 表示当前逗号的位置
public void dfs(int i) {
if (i == s.length()) {
res.add(new ArrayList<>(path));
return;
}
for (int j = i; j < s.length(); j++) {
if (isPalindrome(i, j)) {//判断当前是否是
path.add(s.substring(i, j + 1));
dfs(j + 1);
path.remove(path.size() - 1);//回溯
}
}
}
public boolean isPalindrome(int left, int right) {
while (left < right) {
if (s.charAt(left++) != s.charAt(right--)) {
return false;
}
}
return true;
}
}
61-70
61.N皇后
62.搜索插入位置
public class Code61 {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target) {
right = right - 1;
} else if (nums[mid] < target) {
left = left + 1;
} else {
return mid;
}
}
return left ;
}
}
63.搜索二维矩阵
public class Code62 {
public boolean searchMatrix(int[][] matrix, int target) {
//两次二分查找,或者转换int x = matrix[mid / n][mid % n];,或者排除法从后面排除列
int i = searchInsert(matrix, target); // 找到目标所在的行
if (i < 0 || i >= matrix.length) {
return false;
}
return searchInsert(matrix[i], target); // 在该行查找目标
}
public int searchInsert(int[][] nums, int target) {
int n = nums[0].length;
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid][n - 1] > target) {
right = mid - 1;
} else if (nums[mid][n - 1] < target) {
left = mid + 1;
} else {
return mid;
}
}
return left;
}
public boolean searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return true;
}
}
return false;
}
}
64.在排序数组中查找元素的第一个和最后一个位置
public class Code64 {
public int[] searchRange(int[] nums, int target) {
// 去二分搜索左边的在不在,不在直接返回,如果在再去搜搜target+1的坐标也就是left
int b = bs(nums, target);
if (nums[b] != target) {
return new int[]{-1, -1};
}
return new int[]{b,bs(nums, target + 1)};
}
public int bs(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] >= target) { // 这里保证多个重复只会搜索到第一个,缩小的是有边界,往左继续搜,然后让l+1
r = mid - 1;
} else if (nums[mid] < target) {
l = mid + 1;
}
}
return l;
}
}
65.搜索旋转排序数组
public class Code65 {
public int search(int[] nums, int target) {
// 搜索旋转数组的最小值,然后进行分段二分找值
int index = findMin(nums);
int flag = bs(nums, target, 0, index-1);
if (flag != -1) {
return flag;
}
return bs(nums, target, index , nums.length -1 );
}
//二分搜索,去找最右边的值进行边界判断
public int findMin(int[] nums) {
int l = 0, r = nums.length -1 ;
while (l < r) { //注意停止条件
int mid = (l + r) / 2;
if (nums[mid] >= nums[nums.length - 1]) {
l = mid + 1;
} else {
r = mid ; //当前值也有可能是最小值需要计算
}
}
return l ;
}
public int bs(int[] nums, int target, int l, int r) {
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] > target) {
r = mid - 1;
} else if (nums[mid] < target) {
l = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
66.寻找旋转排序数组的最小值
//上面一题的一种思路,注释看上面
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r) / 2;
if(nums[mid] >= nums[nums.length -1]){
l = mid +1;
}else{
r = mid;
}
}
return nums[l] ;
}
67.寻找两个正序数组的中位数
68.有效括号
public class Code68 {
public boolean isValid(String s) {
//利用栈结构,遇到左括号就存右括号,否则弹出判断是否一样的括号,消消乐
Deque<Character> q = new ArrayDeque<>();
if (s.length() % 2 != 0) {
return false;
}
for (char c : s.toCharArray()) {
if (c == '{') {
q.push('}');
} else if (c == '[') {
q.push(']');
} else if (c == '(') {
q.push(')');
} else if (q.isEmpty() || q.pop() != c) {
return false;
}
}
return q.isEmpty();
}
}
69.最小栈
class MinStack {
//最小栈,保证获取的时候是最小值,因此我需要维护一个前缀和的最小值,所以我栈里面存数组
//需要初始化里面的前缀值是最大数
private final Deque<int[]> q = new ArrayDeque<>();
public MinStack() {
q.push(new int[]{0,Integer.MAX_VALUE});
}
public void push(int val) {
q.push(new int[]{val , Math.min(val,getMin())});
}
public void pop() {
q.pop();
}
public int top() {
return q.peek()[0];
}
public int getMin() {
return q.peek()[1];
}
}
70.字符串解码
public class Code70 {
public String decodeString(String s) {
/* 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
构建两个栈,resStack 存储「数字前的字符串」,multiStack 存储「括号前的数字」。
两个变量,multi 暂时保存「遍历到的连续数字(即一个完整的整数)」,
res 暂时保存「数字前的字符串」或「括号里的字符串」。
当 c 为字母时,在 res 尾部添加 c;
当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 0:
记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × [...] 字符串。muti会十进制运算
进入到新 [ 后,res 和 multi 重新记录。
当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
last_res是上个 [ 到当前 [ 的字符串,例如 "3[a2[c]]" 中的 a;
cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 "3[a2[c]]" 中的 2*/
Deque<Integer> mutiS = new ArrayDeque<>();
Deque<String> resS = new ArrayDeque<>();
int muti = 0;
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if (c >= '0' && c <= '9') {
muti = 10 * muti + c - '0'; //如果两个都是数字
} else if (c >= 'a' && c <= 'z') {
sb.append(c);
} else if (c == '[') { //遇到左边,就把当前数据加入两个栈里面,然后清空
resS.push(sb.toString());
mutiS.push(muti);
sb.setLength(0); // 清空sb
muti = 0;
} else if (c == ']') {//遇到右边就把两个都弹出,这个的弹出的数字n是接下里弹出的字符串的n倍进行遍历
int cnt = mutiS.pop();
String res = sb.toString();
while (cnt > 1) {
sb.append(res);
cnt--;
}
if (resS.isEmpty()) {
continue;
}
String pop = resS.pop();
sb.insert(0, pop);
}
}
return sb.toString();
}
}
71-80
71.每日温度
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
// 从后往前走,栈里面从后开始存索引,大于栈顶值就不断弹出,小于栈顶值就加入,然后相减
Deque<Integer> q = new ArrayDeque<>();
int n = temperatures.length;
int[] ans = new int[n];
for (int i = n - 1; i >= 0; i--) {
int t = temperatures[i];
while(!q.isEmpty()&&t >= temperatures[q.peek()]){
q.pop();
}
if(!q.isEmpty()){
ans[i] = q.peek() - i ;
}
q.push(i);
}
return ans;
}
}
72.柱状图中的最大矩形
73.数组中的第K个最大元素
class Solution {
public int findKthLargest(int[] nums, int k) {
sort(nums,0,nums.length-1 );
return nums[nums.length - k];
}
public static void sort(int[] arr, int start, int end) {
//注意思路,两种交换,并且注意移动指针的范围变化
if (start < end) {
int index = partition(arr, start, end);//获取分区
sort(arr, start, index - 1);
sort(arr, index + 1, end);
}
}
public static int partition(int[] arr, int low, int high) {
int pivot = arr[low];
int i = low + 1;
int j = high;
while (i <= j) {
//找到第一个比基准大的元素
while (i <= j && arr[i] < pivot) {
i++;
}
while (i <= j && arr[j] > pivot) {
j--;
}
if (i <= j) {
swap(arr, i, j);
i++;
j--;
}
}
swap(arr, low, j);
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
74.前K个高频元素
class Solution {
public int findKthLargest(int[] nums, int k) {
sort(nums,0,nums.length-1 );
return nums[nums.length - k];
}
public static void sort(int[] arr, int start, int end) {
//注意思路,两种交换,并且注意移动指针的范围变化
if (start < end) {
int index = partition(arr, start, end);//获取分区
sort(arr, start, index - 1);
sort(arr, index + 1, end);
}
}
public static int partition(int[] arr, int low, int high) {
int pivot = arr[low];
int i = low + 1;
int j = high;
while (i <= j) {
//找到第一个比基准大的元素
while (i <= j && arr[i] < pivot) {
i++;
}
while (i <= j && arr[j] > pivot) {
j--;
}
if (i <= j) {
swap(arr, i, j);
i++;
j--;
}
}
swap(arr, low, j);
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
75.数据流的中位数
76.买入股票的最佳时机
class Solution {
//贪心做法,拿遍历路上的最小值来对后面减掉这个价的最大值就是最大利润
public static int maxProfit(int[] prices) {
int min = Integer.MAX_VALUE ;
int profit = 0;
for (int i = 0; i < prices.length; i++) {
min = Math.min(min , prices[i]);
profit = Math.max(profit, prices[i] - min);
}
return profit;
}
}
77.跳跃游戏
class Solution {
public boolean canJump(int[] nums) {
//每走一步-1,然后对应+上当前值,更新step找大值,返回的step只能大于等于0
int n = nums.length;
if(n ==1 ){
return true;
}
int step = nums[0];
for (int i = 1; i < n; i++) {
step--;
if(step>=0&&step <= nums[i]) {
step = nums[i];
}
}
return step >= 0 ;
}
}
78.跳跃游戏II
class Solution {
public int jump(int[] nums) {
//贪心,每次我都走到最远的最远的距离
int max = 0;
int step = 0;
int end = 0; // end表示上个起跳点能够到达的最远距离
for (int i = 0; i < nums.length -1 ; i++) {//这里最后一次不需要跳很关键
max = Math.max(max, i + nums[i]);//更新当前起跳点最大取值
if(i ==end){//经过上个点最远距离
end = max;
step++;
}
}
return step ;
}
}
79.划分字母区间
80.爬楼梯
//数组斐波那契
public class Code80 {
public int climbStairs(int n) {
if (n < 3) {
return n;
}
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
81-90
81.杨辉三角
class Solution {
public List<List<Integer>> generate(int numRows) {
//注意前后都要添加1 ,除了1的之外就是集合直接操作,不用换数组
//画张图并排看一下,然后当前值等于上面和左上角的值相加
List<List<Integer>> res = new ArrayList<>(numRows);
List<Integer> list = new ArrayList<>();
list.add(1);
res.add(list);
for (int i = 1; i < numRows; i++) {
List<Integer> tmp = new ArrayList<>();
tmp.add(1);
for (int j = 1; j < i; j++) {
tmp.add(res.get(i - 1).get(j) + res.get(i - 1).get(j - 1));
}
tmp.add(1);
res.add(tmp);
}
return res;
}
}
82.打家劫舍
class Solution {
public int rob(int[] nums) {
int[] dp = new int [nums.length];
int n = nums.length;
if(n == 0) return 0;
if(n == 1) return nums[0];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1] , dp[i-2] + nums[i]);
}
return dp[nums.length-1];
}
}
83.完全平方数
class Solution {
public static int numSquares(int n) {
//dp[i]表示i的最小值,那么只需要去对比前面的状态
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = i;//最差也是i
for (int j = 0; j * j<= i; j++) {
dp[i] =Math.min(dp[i],dp[i - j * j] + 1);
}
}
return dp[n];
}
}
84.零钱兑换
class Solution {
public int coinChange(int[] coins, int amount) {
int n = coins.length;
//表示n个货币组成对应价值需要的最小硬币数,完全背包
int[][] dp = new int[n +1][ amount +1 ];
Arrays.fill(dp[0],Integer.MAX_VALUE /2 );
dp[0][0] = 0;
//枚举的话这边硬币需要被枚举0索引,所以访问的事coin[i-1]的操作
//然后最大值那块以后都去进行一个/2操作防止+1越界
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if(j < coins[i-1 ]) {
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.min(dp[i-1][j] , dp[i][j - coins[i-1 ]] +1 );
}
}
}
int ans = dp[n][amount];
return dp[n][amount] == Integer.MAX_VALUE /2 ? -1 : dp[n][amount];
}
}
85.单词拆分
class Solution {
public static boolean wordBreak(String s, List<String> wordDict) {
//用dp[i]记录到i是否可以通过字典构成
int length = s.length();
boolean[] dp = new boolean[length + 1];
dp[0] = true;//0可以通过任何构成
for (int i = 0; i <= length; i++) {
if(!dp[i]){//这里需要记录只有他前面是ture后面接上才有意义
continue;
}
for (String word : wordDict) {
if(word.length() + i <= s.length()&&s.startsWith(word,i)){
dp[i+ word.length()] = true;//内循环遍历子串注意startwith要带偏移量
}
}
}
return dp[length];
}
}
86.最长递增子序列
class Solution {
public static int lengthOfLIS(int[] nums) {
//dp[i]表示到i包括nums[i],当中最长的递增子序列,那么就是两个循环去跑dp[i] = max(dp[j]) +1
int n = nums.length;
int[] dp = new int[n +1 ];
Arrays.fill(dp, 1);//所有单个都是1 的序列
int max = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if(nums[i] > nums[j]){//这里大于才进行递归
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
max = Math.max(max, dp[i]);//保存dp里面最大值,动画看官方
}
return max;
}
}
87.乘数最大子数组
class Solution {
public static int maxProduct(int[] nums) {
//维护最小值和最大值,因为最小可能变最大(负数)
if(nums.length == 0) return 0;
int[] dpMin = new int[nums.length];
int[] dpMax = new int[nums.length];
dpMin[0] = nums[0];
dpMax[0] = nums[0];
int max = nums[0];
for(int i = 1; i < nums.length; i++) {
int tmp1 = dpMax[i-1]*nums[i];//正数变大
int tmp2 = dpMin[i-1]*nums[i];//负负得正
int tmp3 = nums[i];//遇到0后就是本身
dpMax[i] = Math.max(Math.max(tmp1, tmp2), tmp3);
dpMin[i] = Math.min(Math.min(tmp1, tmp2), tmp3);
max = Math.max(max, dpMax[i]);
}
return max;
}
}
88.分割等和子集
class Solution {
public boolean canPartition(int[] nums) {
//总和奇数,最大值大于一半这些都是不能存在的
//01 背包,选和不选的问题,并且选的话需要减去对应的价值
//这里的容量和价值都是nums,并且内循环需要从大往小遍历防止覆盖
int n = nums.length;
int sum = 0 , max = 0;
for (int num : nums) {
sum += num;
max = Math.max(max, num);
}
if(sum % 2 != 0) return false;
int target = sum / 2;
if(max > target) return false;
int dp[] =new int [target+1];
dp[0] = 0;
for (int i = 1; i < n; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
89.最长有效括号
90.矩阵置零
这个应该插在18
public class Code17 {
public void setZeroes(int[][] matrix) {
// 遍历两次,然后把对应的地方记录行列,第二次遍历的时候就是弄0
int m = matrix.length;
int n = matrix[0].length;
boolean[] row = new boolean[m];
boolean[] column = new boolean[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++){
if(matrix[i][j]==0){
row[i] = true;
column[j] = true;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++){
if(row[i]||column[j]){
matrix[i][j] = 0;
}
}
}
}
}
91-100
91.不同路径
class Solution {
public static int uniquePaths(int m, int n) {
//思路一是组合数学,下的作为插入点,也就是cmn
int[][] dp = new int[m + 1][n + 1];
dp[1][0] = 1;//只需要初始化一个,负责会重复计算
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
}
}
return dp[m][n];
}
}
92.最小路径和
class Solution {
public static int minPathSum(int[][] grid) {
//也是从上和左边的递归式子
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m+1][n+1];
dp[1][1] = grid[0][0];//初始化对应的节点
for(int i = 1; i <= m; i++){
for(int j = 1; j <=n; j++){
if(i == 1 && j == 1) continue;//需要分类讨论边界点
else if(i == 1) dp[i][j] = dp[i][j - 1] + grid[i-1][j-1];
else if(j == 1) dp[i][j] = dp[i - 1][j] + grid[i-1][j-1];
else dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i-1][j-1];
}
}
return dp[m][n];
}
}
93.最长回文子串
class Solution {
public static String longestPalindrome(String s) {
//dp[i][j]表示i-j是回文,他通过dp[i-1][j-1]且s[i]==s[j]成立就能转移
//由于长度必须大于2 ,这边可以从长度外循环,左指针内循环,可以构造出对应的右边指针
int len = s.length();
if(len < 2) return s;
int max = 1 ;
int begin = 0 ;
boolean[][] dp = new boolean[len][len];
for (int i = 0; i < dp.length; i++) {
dp[i][i] = true;//所有长度为1的都是回文
}
for (int L = 2; L <= len; L++) {
for (int i = 0; i < len; i++) {
int j = i + L -1;//右指针+1
if(j>=len) break;
if(s.charAt(i)==s.charAt(j)){//两个值相等
//这里如果是两个的字符串那么就是true不用转移
if(j-i <=2){
dp[i][j] = true;
}else{
dp[i][j] = dp[i+1][j-1];
}
}else{
dp[i][j] = false;
}
if(dp[i][j]&&j - i + 1 > max){//更新最大长度
max = j - i + 1;
begin = i;
}
}
}
return s.substring(begin,begin+max);
}
}
94.最长公共子序列
class Solution {
public static int longestCommonSubsequence(String text1, String text2) {
//dp[i][j]表示对text1中0 - i ,以及text2中0 -j 中最长公共子序列元素个数
int m = text1.length();
int n = text2.length();
int[][]dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
}
95.编辑距离
class Solution {
public static int minDistance(String word1, String word2) {
//定义dp[i][j]是对应的word1 0 - i 变成word2 0- j 最小需要的步数
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
//这边需要初始化,也就是全部删,增的情况
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int i = 0; 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];
}
}
96.只出现一次的数字
class Solution {
public int singleNumber(int[] nums) {
int ans =nums[0];
for(int i = 1;i < nums.length;i++){
ans ^=nums[i];
}
return ans;
}
}
97.多数元素
class Solution {
public int majorityElement(int[] nums) {
//排序取中值,或者随机取一个数判断是否是众数
Arrays.sort(nums);
int n = nums.length;
return nums[n/2];
}
}
98.颜色分类
class Solution {
public void sortColors(int[] nums) {
sort(nums,0,nums.length -1 );
}
public static void sort(int[] arr, int start, int end) {
//注意思路,两种交换,并且注意移动指针的范围变化
if (start < end) {
int index = partition(arr, start, end);//获取分区
sort(arr, start, index - 1);
sort(arr, index + 1, end);
}
}
public static int partition(int[] arr, int low, int high) {
int pivot = arr[low];
int i = low + 1;
int j = high;
while (i <= j) {
//找到第一个比基准大的元素
while (i <= j && arr[i] < pivot) {
i++;
}
while (i <= j && arr[j] > pivot) {
j--;
}
if (i <= j) {
swap(arr, i, j);
i++;
j--;
}
}
swap(arr, low, j);
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public class Code97 {
public int majorityElement(int[] nums) {
//排序取中值,或者随机取一个数判断是否是众数
Arrays.sort(nums);
int n = nums.length;
return nums[n/2];
}
}
99.下一个排列
class Solution {
public void nextPermutation(int[] nums) {
//第一步先把从右往左找到第一个左边小于右边的数x
int n = nums.length;
int i = n - 2;
while (i>=0&&nums[i] >= nums[i + 1]) {
i--;
}
//第二步筛选出x右边大于x的最小数字(x后面都是单调递减),并进行交换
int j = n - 1;
//
while (i>=0&&nums[j] <= nums[i]) {
j--;
}
if(i>= 0){
swap(nums, i, j);
}
//最后把x后的数字进行反转
int left = i + 1;
int right = n - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
100.寻找重复数
class Solution {
public int findDuplicate(int[] nums) {
//快慢指针找入口环节点,节点关系用数组索引解决
int slow = 0, fast = 0 ;
slow = nums[slow];
fast = nums[nums[fast]];
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
int pre1 = 0 ;
int pre2 = slow;
while(pre1!=pre2){
pre1 = nums[pre1];
pre2 = nums[pre2];
}
return pre1;
}
}